First we need to set up the environment and load the packages we will use for this workshop.

library(Seurat): Loads the Seurat package, which is a comprehensive toolkit for single-cell RNA sequencing and spatial transcriptomics data analysis. It provides a wide range of functions for data preprocessing, normalization, clustering, dimensionality reduction, and visualization. Explore documentation here: https://satijalab.org/seurat/

library(ggplot2): Loads the ggplot2 package, a powerful and flexible system for creating static visualizations in R. Explore documentation here: https://ggplot2.tidyverse.org/

library(scCustomize): Loads the scCustomize package, which provides custom functions and themes to enhance the visualization and analysis capabilities of single-cell and spatial transcriptomics data, often in conjunction with Seurat. Explore documentation here: https://samuel-marsh.github.io/scCustomize/

library(readr): Loads readr package for fast and friendly reading of rectangular data, such as CSV files, into R.

library(pheatmap): Loads pheatmap package, which is for creating pretty heatmaps, offering better control over heatmap customization compared to base R.

library(matrixStats): matrixStats provides highly optimized functions for matrix operations, particularly useful for computing row and column summaries.

library(spdep): spdep stands for Spatial Dependence and Spatial Autocorrelation, and it provides functions for spatial data analysis, including spatial weights generation, spatial autocorrelation statistics, and spatial regression.

library(geojsonR) The geojsonR library is used for handling GeoJSON data in R. GeoJSON is a format for encoding a variety of geographic data structures using JavaScript Object Notation (JSON). It is sometimes used as a format for storing cell segmentation boundaries.

library(Seurat)
library(ggplot2)
library(scCustomize)
library(readr)
library(pheatmap)
library(matrixStats)
library(spdep)
library(geojsonR)

Sets the path to the directory containing the Xenium output data - this is the directory where all of the outputs are stored.

data_dir <- "/project/shared/spatial_data_camp/datasets/DATASET2/XENIUM_COLORECTAL_CANCER/"

ReadXenium reads Xenium spatial transcriptomics data from a specified directory using a Seurat wrapper function that supports this data format. Xenium data typically includes expression matrices and spatial coordinates, along with other information about cell centroids and segmentations and coordinates of individual transcripts.

data_dir: The path to the directory containing the Xenium data, set in the previous step. outs = c(“matrix”, “microns”): Specifies the outputs to read from the data directory. matrix refers to summarised cell by gene matrix and microns refers to individual transcript coordinates.

type = c(“centroids”, “segmentations”): Indicates the types of spatial information to include - here, we are reading ib both cell centroid coordinates and cell boundary segmentations.

data <- ReadXenium(data_dir, outs = c("matrix", "microns"), type=c("centroids", "segmentations"))
10X data contains more than one type and is being returned as a list containing matrices of each type.
|--------------------------------------------------|
|==================================================|

This provides us a list of data:

names(data)
[1] "matrix"        "microns"       "centroids"     "segmentations"

Matrix is further split into gene expression matrix and various control probes and codewords. Different platforms and platform versions include different control probes. As this will vary, it’s important to check and understand what the specific controls in your own data are.

Here, negative control probes are probes that are added to the reaction but target non-biological sequences and should not bind any tissue RNA. Negative control codewords are valid codewords, but no probes with that codeword added to the reaction. This effectively tells us how good the transcript calling algorithm is.

names(data$matrix)
[1] "Gene Expression"           "Negative Control Codeword" "Negative Control Probe"   
[4] "Unassigned Codeword"      

Read in additional information about the cells - this gives us pre-calculated information, for example segmented cell or nucleus size for each cell.

cell_meta_data <- read.csv(file.path(data_dir, "cells.csv.gz"))
rownames(cell_meta_data) <- cell_meta_data$cell_id
head(cell_meta_data)

We will start by creating a basic seurat object from the data.

CreateSeuratObject function initializes a Seurat object using the provided gene expression matrix and optional metadata.

counts: The gene expression matrix, which contains the raw count data for each gene in each cell. data$matrix[[“Gene Expression”]]: Specifies the gene expression matrix extracted from the loaded Xenium data. Here, we leave out the control probes for now.

assay: The name of the assay - you can call it anything you like. Here, we go with “XENIUM”.

meta.data: Metadata associated with the cells or spots. Here, we add the cell statistics we read in earlier as cell_meta_data.

By printing the seurat object, we can see that we read in ~ 30,000 cells with measures for 325 genes

seurat <- CreateSeuratObject(counts = data$matrix[["Gene Expression"]],
                                 assay = "XENIUM",
                                 meta.data = cell_meta_data)
seurat
An object of class Seurat 
325 features across 647524 samples within 1 assay 
Active assay: XENIUM (325 features, 0 variable features)
 1 layer present: counts

Adding spatial coordinates to a Seurat object allows for spatially resolved analysis and visualization. This requires creating objects for centroids and segmentations we read in earlier, and then integrating these with the main Seurat object.

CreateFOV: This function creates a field of view (FOV) object that includes spatial information about the centroids, segmentations, and molecule coordinates. An FOV can be the entire slide, or a selected region within a slide - i.e. it does not need to have entries for all the cells in the seurat object.

coords: A list containing the centroids and/or segmentation data. For larger datasets, it can be quicker to only load centroids, as this minimises the amount of data points.

centroids = CreateCentroids(data\(centroids)*: Creates a centroids object from the centroid data in the Xenium dataset. *segmentation = CreateSegmentation(data\)segmentations): Creates a segmentation object from the segmentation data in the Xenium dataset.

type = c(“segmentation”, “centroids”): Specifies the types of spatial data being included, which are segmentation and centroid data.

molecules = data$microns: The spatial coordinates of individual transcripts/molecules in the data. This is optional - for larger datasets, skipping transcript coordinates can be a good idea.

seurat[[“COLON”]] <- coords: Adds the created FOV object to the Seurat object under the new FOV name “COLON”. This can be named (almost) anything - but, avoid using underscores as this can create some unexpected behaviours later.

TIP: LoadXenium() is a wrapper that would load in both cell counts matrix and spatial coordinates in one function, simplifying these steps. However, in situ platforms are evolving at a very fast rate and there are constant changes on how the data is stored, in particular for file formats for cell segmentation and coordinates. Here, we have broken down the steps to show how to assemble an in situ seurat object from the key components, in case the platform specific readers don’t work for your specific data.

coords <- CreateFOV(coords = list(centroids = CreateCentroids(data$centroids), 
                                  segmentation = CreateSegmentation(data$segmentations)),
                    type = c("segmentation", "centroids"),
                    molecules = data$microns,
                    assay = "XENIUM")
seurat[["COLONC2"]] <- coords  

Inspect the object - now, you can see we have added a spatial field of view:

To subset the object

Adding control probes and codewords as separate assays in the Seurat object allows for the tracking and analysis of technical artifacts and noise within your spatial transcriptomics data, while keeping these outputs separate from the main biological gene expression values.

Unassigned codewords are unused codewords. There is no probe in a particular gene panel that will generate the codeword.

Negative control probes are probes that exist in the panels but target non-biological sequences. They can be used to assess the specificity of the assay.

Negative control codewords are codewords in the codebook that do not have any probes matching that code. They are chosen to meet the same requirements as regular codewords and can be used to assess the specificity of the decoding algorithm.

seurat[["Negative.Control.Codeword"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Codeword"]])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')
seurat[["Negative.Control.Probe"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Probe"]])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')
seurat[["Unassigned.Codeword"]] <- CreateAssayObject(counts = data$matrix[["Unassigned Codeword"]])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')

subset an object

seurat #read the object
An object of class Seurat 
541 features across 647524 samples within 4 assays 
Active assay: XENIUM (325 features, 0 variable features)
 1 layer present: counts
 3 other assays present: Negative.Control.Codeword, Negative.Control.Probe, Unassigned.Codeword
 2 spatial fields of view present: COLONC2 CRC2

Let’s start with some basic QC and visualisation of the data.

In Seurat, in situ spatial transcriptomics counterpart functions to ‘SpatialDimPlot’ and ‘SpatialFeaturePlot’ we covered yesterday are called ‘ImageFeaturePlot’ and ‘ImageDimPlot’. These have additional functionality to plot cell segmentations and individual transcript coordinates, but otherwise function exactly the same as the sequencing based ST counterparts.

First, lets visualise the total transcripts detected per cell.

As in scRNA-Seq data, this is the most basic measure of overall signal and how well the data looks.

Unlike in scRNA-Seq data or unbiased sequencing-based ST, these measures are also very heavily dependent not only on the total RNA quantity of each cell and tissue quality, but also on the target panel used for the experiment. Under-represented cell types will naturally yield fewer transcripts. Finally, the quality of cell segmentation also plays a role.

In this case, we can see that there are areas with higher and lower total transcripts detected.

Understanding your tissue and target panel here is important to delineate where these differences are biological and where they may be technical.

Similarly, we can visualise the total number of gene detected per cell. You can see that this is a bit less variable across tissue.

This can also suggest that there cells at the top of the epithelial crypts in this sample with genes detected at high copy number than the rest of the tissue.

ImageFeaturePlot(seurat_CRC2, "nFeature_XENIUM", axes = T) + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

This code examines the distribution of the number of features (genes) detected per cell in the Seurat object using a density plot and calculates specific quantiles of this distribution. This is important for understanding the variability and distribution of detected features, which can help identify potential issues such as low-quality cells and determine any filtering thresholds that may need to be applied.

If you’re coming from scRNA-Seq work, these low numbers probably look very alarming. How can you possibly work with 31 median genes per cell?

Unlike scRNA-Seq data and sequencing-based ST, both gene dropouts and noise are much, much lower in in situ ST data.

We are also working with 100-fold fewer targetted genes.

quantile(seurat_CRC2$nFeature_XENIUM, c(0.01, 0.1, 0.5, 0.9, 0.99))
 1% 10% 50% 90% 99% 
  5  14  33  56  76 

Using ImageFeaturePlot to visualize the cell area in spatial transcriptomics data allows us to examine the spatial organization and potential heterogeneity of cell sizes within your tissue sample.

Why do we get such a difference in spatial distribution of cell sizes?

This could be due to biological differences between small and large cells - e.g. small cells like T-cells.

However, here the signal correlates with areas of low cellularisation. Therefore, it is likely this is an artefact of nuclei expansion in cell segmentation.

What is Nuclei Expansion?

Nuclei expansion in cell segmentation refers to the process of enlarging the segmented nuclei regions to approximate the boundaries of the entire cells. This technique is used to better represent the actual cell boundaries when only the nuclei have been explicitly segmented/we only have DAPI and no additional cell boundary staining. The primary goal is to provide a more accurate estimation of the cellular area, which is crucial for various downstream analyses in spatial transcriptomics and single-cell studies. In this case, nuclei expansion is constrained either by maximum distance or other nearby cells - so, where there are no other nearby cells to “bump into”, the expansion generates artificially bigger cells.

ImageFeaturePlot(seurat_CRC2, "cell_area", axes = T) + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

We can further check that this is likely the case by plotting the ratio between nuclei and total cell area. We can see that there is a very big decrease in percentage of cell area occupied by nucleus in areas of low cell density.

The cell-to-nucleus area ratio can also potentially provide insights into cell morphology, cell type and potential changes in cellular states or conditions. For example, T-Cells can often be quite well identified by this variable alone, as they have a small cytoplasm volume. However, without a cell boundary stain, this metric mainly captures segmentation artefacts, so be careful about over-interpretation!

ImageFeaturePlot(seurat_CRC2, "cell_nucleus_ratio") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

If we look at the distribution, we see that we have a big tail end of overly large cells.

In this case, we can see that as expected, there is generally a correlation between cell area and transcript detection rate.

However, we also have a group of cells where this is not the case - very large cells but relatively few transcripts. These cells are mainly submucosal stromal cells which are very poorly covered by the panel 10x have used.

We can create a filter to remove the overly large cells from the analysis.

quantile(seurat$cell_area, 0.99): Calculates the 99th percentile of the cell_area values in the Seurat object. This value serves as a threshold to identify the largest 1% of cells - but what is a sensible threshold, if any, depends on your tissue.

seurat\(cell_area < quantile(seurat\)cell_area, 0.99): Compares each cell’s area to the 99th percentile threshold. The result is a logical vector where each element is TRUE if the corresponding cell’s area is less than the 99th percentile and FALSE otherwise.

seurat[[“SIZE_FILTER_LARGE”]]: Creates a new metadata field named SIZE_FILTER_LARGE in the Seurat object, storing the logical vector.

seurat_CRC2[["SIZE_FILTER_LARGE"]] <- seurat_CRC2$cell_area < quantile(seurat_CRC2$cell_area, .99)

Now we can use ImageDimPlot to visualise the cells which have been flagged for removal.

We can see that these are mostly in the submucosa region.

How do different thresholds behave? Is there a more appropriate one to use? Is any necessary at all?

ImageDimPlot(seurat_CRC2, group.by="SIZE_FILTER_LARGE")

We can use the same approach to create a filter for segmented cells which are very small and likely segmentation arfetacts.

quantile(seurat$cell_area, 0.01): Calculates the 1st percentile of the cell_area values in the Seurat object. This value serves as a threshold to identify the smallest 1% of cells.

seurat\(cell_area > quantile(seurat\)cell_area, 0.01): Compares each cell’s area to the 1st percentile threshold. The result is a logical vector where each element is TRUE if the corresponding cell’s area is greater than the 1st percentile and FALSE otherwise.

seurat[[“SIZE_FILTER_SMALL”]]: Creates a new metadata field named SIZE_FILTER_SMALL in the Seurat object, storing the logical vector.

seurat_CRC2[["SIZE_FILTER_SMALL"]] <- seurat_CRC2$cell_area > quantile(seurat_CRC2$cell_area, .01)

Now we can use ImageDimPlot to visualise the cells which have been flagged for removal.

We can see that these are more scattered throughout the tissue - but there may be more in the follicular regions.

How do different thresholds behave? Is there a more appropriate one to use? Is any necessary at all?

We can check how these values correlate with gene detection rate.

If we filter out small cells, we will remove cells with low numbers of genes detected.

If we filter out large cells, this is not that biased towards overly large counts, as we saw before.

Adjusting the threshold for what is considered a “small cell” can have significant implications for your analysis, especially in areas with specific cell types such as T-cells, which are small and densely packed in follicular regions. This example demonstrates how changing the threshold to the 10th percentile affects the filtering. In this case, we would probably filter out a lot of good cells that we don’t want to lose! So, be careful when looking at these types of QC metrics!

seurat_CRC2[["SIZE_FILTER_SMALL"]] <- seurat_CRC2$cell_area > quantile(seurat_CRC2$cell_area, .1)
ImageDimPlot(seurat_CRC2, group.by="SIZE_FILTER_SMALL")

Lets set this back to the original 1% threshold.

seurat_CRC2[["SIZE_FILTER_SMALL"]] <- seurat_CRC2$cell_area > quantile(seurat_CRC2$cell_area, .01)

The most important filter is the overall transcript detection. Empty cells or cells with very low transcript count cannot be taken forward for clustering analysis and it is extremely difficult to identify what they may be. Here, we set a threshold of minimum 15 transcripts. This seems quite low - for data from in situ platforms with low noise (Xenium, Merfish, Merscope), this is generally enough to cluster and identify cell types. If your data has more noise (e.g. CosMx), a higher threshold is more appropriate.

seurat\(nCount_XENIUM >= 15*: Compares each cell's transcript count to the threshold of 15. The result is a logical vector where each element is TRUE if the corresponding cell has at least 15 transcripts and FALSE otherwise. *seurat\)TRANSCRIPT_FILTER: Creates a new metadata field named TRANSCRIPT_FILTER in the Seurat object, storing the logical vector.

seurat_CRC2$TRANSCRIPT_FILTER <- seurat_CRC2$nCount_XENIUM >= 15

And we can visualise the cells that we would lose.

We see that we disproportionately would filter out more cells from some regions than others. As pointed out previously, this is likely due to a combination of gene panel coverage in some regions and very small cells in densely packed regions like follicles.

ImageDimPlot(seurat_CRC2, group.by="TRANSCRIPT_FILTER")

Finally, visualizing the counts of negative control codewords, negative control probes, and unassigned codewords helps identify and understand technical artifacts and background noise in your spatial transcriptomics data.

Here, we can see that all control probes and codewords produce yield very little signal, suggesting our data is good quality!

In some cases, high amount of autoflourescence is the cells/tissue can sometimes generate false positive signal and this should be filtered out.

ImageFeaturePlot(seurat_CRC2, "nCount_Negative.Control.Codeword") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "nCount_Negative.Control.Probe") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "nCount_Unassigned.Codeword") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Although the negative control signal is low, we can nonetheless create a filter to remove cells which have any, although in this case it is probably unnecessary.

seurat_CRC2$PROBE_FILTER <- seurat_CRC2$nCount_Unassigned.Codeword == 0 &
                       seurat_CRC2$nCount_Negative.Control.Codeword == 0 &
                       seurat_CRC2$nCount_Negative.Control.Probe == 0
ImageDimPlot(seurat_CRC2, group.by="PROBE_FILTER")

Finally, we can subset the seurat object based on any/all of the filters we have created earlier.

By combining probe, size, and transcript filters, you can retain only the cells that meet all quality criteria, reducing the impact of technical artifacts and noise on your analysis.

seurat_CRC2 <- subset(seurat_CRC2, PROBE_FILTER & SIZE_FILTER_LARGE & SIZE_FILTER_SMALL & TRANSCRIPT_FILTER)
Warning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating Seurat objects

Lets examine the cleaned up object - we have lost a few thousand cells from the analysis.

Data Normalisation

The SCTransform function in Seurat is used for normalizing single-cell RNA-seq and spatial transcriptomics data. This method models the gene expression counts using a regularized negative binomial regression and removes technical noise while preserving biological variability. The clip.range parameter is used to limit the range of the transformed values, which can help stabilize downstream analyses by limiting the influence of extreme values.

seurat_CRC2 <- SCTransform(seurat_CRC2, assay = "XENIUM", clip.range = c(-10, 10))
Running SCTransform on assay: XENIUM
Running SCTransform on layer: counts
vst.flavor='v2' set. Using model with fixed slope and excluding poisson genes.
Variance stabilizing transformation of count matrix of size 325 by 73985
Model formula is y ~ log_umi
Get Negative Binomial regression parameters per gene
Using 320 genes, 5000 cells
Found 38 outliers - those will be ignored in fitting/regularization step

Second step: Get residuals using fitted parameters for 325 genes
Computing corrected count matrix for 325 genes
Calculating gene attributes
Wall clock passed: Time difference of 21.71334 secs
Determine variable features
Centering data matrix

  |                                                                                       
  |                                                                                 |   0%
  |                                                                                       
  |=================================================================================| 100%
Getting residuals for block 1(of 15) for counts dataset
Getting residuals for block 2(of 15) for counts dataset
Getting residuals for block 3(of 15) for counts dataset
Getting residuals for block 4(of 15) for counts dataset
Getting residuals for block 5(of 15) for counts dataset
Getting residuals for block 6(of 15) for counts dataset
Getting residuals for block 7(of 15) for counts dataset
Getting residuals for block 8(of 15) for counts dataset
Getting residuals for block 9(of 15) for counts dataset
Getting residuals for block 10(of 15) for counts dataset
Getting residuals for block 11(of 15) for counts dataset
Getting residuals for block 12(of 15) for counts dataset
Getting residuals for block 13(of 15) for counts dataset
Getting residuals for block 14(of 15) for counts dataset
Getting residuals for block 15(of 15) for counts dataset
Centering data matrix

  |                                                                                       
  |                                                                                 |   0%
  |                                                                                       
  |=================================================================================| 100%
Finished calculating residuals for counts
Set default assay to SCT

Principal Component Analysis (PCA) is a dimensionality reduction technique used to identify the primary axes of variation in high-dimensional data. In the context of spatial transcriptomics, PCA helps to reduce the complexity of the data while preserving the most important patterns of variation.

TIP: If your target panel is very small, you can skip this step and carry out clustering analysis directly on gene expression. This can sometimes help with achieving better clustering results.

seurat_CRC2 <- RunPCA(seurat_CRC2)
PC_ 1 
Positive:  IGFBP7, THBS1, TIMP3, DPYSL3, CTSB, MAF, IFITM1, CYBB, VCAN, ETS1 
       ANXA1, CXCR4, PLXND1, CLU, APOE, TRAC, RPS4Y1, SERPINA1, IL7R, MS4A7 
       RNASE1, CD79A, SOCS3, FZD7, TRBC2, DEPP1, CD14, CD3E, CCL5, CD2 
Negative:  CD24, SLC12A2, RRM2, HMGB2, TYMS, PPP1R1B, EPHB3, CDCA7, CA2, FERMT1 
       SOX9, STMN1, PCLAF, C1QBP, REG4, AQP1, CMBL, MKI67, TK1, CEACAM5 
       EGFR, IMPDH2, S100P, SMOC2, CREB3L1, GATA2, UBE2C, MUC12, TUBA1A, LGR5 
PC_ 2 
Positive:  CTSB, APOE, CYBB, RNASE1, MS4A7, SERPINA1, CD14, C1QC, C1QA, CD163 
       C1QB, FYB1, CCL4, MAF, CCL5, IL7R, GPR183, CXCR4, CD83, TRBC2 
       CD8A, TNFSF13B, CD2, CD3E, TRAC, GZMA, CTLA4, PLXND1, TIGIT, CD3D 
Negative:  THBS1, IGFBP7, DPYSL3, TIMP3, VCAN, FZD7, ALDH1B1, AQP1, DEPP1, CLU 
       CD24, RUNX1T1, CES1, SELENOM, CDKN2B, IFITM1, CPE, EPHB3, FRZB, HMGB2 
       CKAP4, RRM2, EGFR, MEIS2, TUBA1A, IMPDH2, TYMS, CA2, SLC12A2, C1QBP 
PC_ 3 
Positive:  MS4A1, TRBC2, TRAC, CD2, CD3E, CXCR4, CD8A, CCL5, GZMK, CD79A 
       SPOCK2, IL7R, CTLA4, GZMA, CD3G, ETS1, TIGIT, CD3D, KLRB1, CD6 
       BANK1, CST7, LTB, NKG7, SPIB, CD5, LRMP, ITK, TRBC1, FOXP3 
Negative:  THBS1, CTSB, IGFBP7, RNASE1, APOE, CD14, MS4A7, CYBB, C1QC, SERPINA1 
       TIMP3, PLXND1, C1QA, CD163, C1QB, VCAN, ALDH1B1, FZD7, DPYSL3, TUBA1A 
       CD24, AQP1, CPE, CES1, SLC12A2, DEPP1, RUNX1T1, SOCS3, CA2, CEACAM5 
PC_ 4 
Positive:  CD79A, MS4A1, CLU, SEC11C, BANK1, CXCR4, LRMP, SPIB, FKBP11, TNFRSF17 
       DERL3, CD79B, RGS13, TCL1A, PRDX4, SMIM14, FCRL1, IRF8, SELENOK, PAX5 
       CYBB, CXCR5, CD83, LTB, SELL, FCER2, GPR183, C2orf88, MS4A7, DPYSL3 
Negative:  CCL5, CD8A, GZMA, CD2, CD3E, TRBC2, TRAC, CTLA4, CD3G, CCL4 
       NKG7, CD3D, THBS1, TIGIT, IGFBP7, KLRB1, GZMK, CST7, CD6, SPOCK2 
       IL7R, MAF, TIMP3, GNLY, FOXP3, CD8B, ITK, CD5, CD24, ID2 
PC_ 5 
Positive:  IGFBP7, TIMP3, IFITM1, PLXND1, AQP1, ETS1, CPE, SEC11C, SOCS3, FKBP11 
       CD79A, PRDX4, VCAN, DERL3, FRZB, TNFRSF17, LEF1, SELENOK, LYVE1, ODF2L 
       ROBO1, CDKN2B, CKAP4, GIMAP7, ANXA1, CA2, TUBA1A, AFAP1L2, TNFRSF25, RNASE1 
Negative:  THBS1, CLU, DPYSL3, ALDH1B1, FZD7, CES1, SELENOM, MS4A1, MAOB, MEIS2 
       CYBB, RUNX1T1, CEACAM5, TRAC, TRBC2, SPIB, CD14, CD2, TCL1A, MS4A7 
       MAF, LTB, CD3G, BANK1, CD8A, DEPP1, CTLA4, CD3E, TIGIT, NOVA1 

As before, we can visualise how much variation is captured by each PC.

The ElbowPlot function helps to determine the number of significant PCs to use for downstream analyses. The plot typically shows the amount of variance explained by each PC, and the “elbow” point indicates a natural cutoff.

ElbowPlot(seurat_CRC2, 50)

Plotting the top genes contributing to a specific principal component helps in understanding the biological factors driving the variation captured by that component. This type of plot highlights the genes with the highest loadings, which are the most influential in the principal component analysis.

PC_Plotting(seurat_CRC2, dim_number = 1)

The FeaturePlot function in Seurat is used to visualize the expression of a specific gene across cells in a given dimensionality reduction space (e.g., PCA). This helps to understand how the expression of a gene varies across the principal components.

FeaturePlot(seurat_CRC2, "CEACAM5", reduction = "pca") + scale_color_viridis_c()
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

We can also examine how various PCs are distributed spatially.

Here, we can see that high PC1 loadings enrich in follicular structures and low PC1 loadings enrich in crypt top cells.

ImageFeaturePlot(seurat_CRC2, "PC_1") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

We can plot the expression of high (or low) loading genes to visualise how this correlates with our dimensionality reduction.

ImageFeaturePlot(seurat_CRC2, "IGFBP7", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Next, we will use the reduced dimensionality data for clustering and cluster visualisation.

RunUMAP: Perform Uniform Manifold Approximation and Projection (UMAP) to reduce the dimensionality of the data for visualization. The UMAP plot reduces the high-dimensional data to two dimensions, preserving the local and global structure of the data for visualization. Cells that are close together in the UMAP plot are similar in their gene expression profiles. seurat: The Seurat object. dims = 1:20: Specifies the principal components to use for UMAP.

FindNeighbors: Finding nearest neighbors helps to identify cells that are similar based on their PCA scores, which is used for clustering. seurat: The Seurat object. reduction = “pca”: Specifies that the PCA space should be used for finding neighbors. dims = 1:20: Specifies the principal components to use for identifying neighbors.

FindClusters: Clustering identifies distinct groups of cells with similar gene expression patterns. The resolution parameter controls the granularity of the clustering. seurat: The Seurat object. resolution = 0.7: Sets the resolution parameter for clustering. Higher values lead to more clusters, while lower values lead to fewer clusters.

seurat_CRC2 <- FindClusters(seurat_CRC2, resolution = 0.2)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 73985
Number of edges: 2363377

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9173
Number of communities: 6
Elapsed time: 52 seconds

Next lets visualise the clusters - firstly, based on transcriptome embedding.

DimPlot: Creates a scatter plot of cells in a reduced-dimensional space, by default now using UMAP dimensionality reduction. seurat: The Seurat object containing the dimensionality reduction results and cluster assignments. label = TRUE: Adds cluster labels to the plot. repel = TRUE: Repels the labels to avoid overlapping, making the plot clearer.

And now lets plot the clusters in tissue space.

We can see that our clusters have quite nice correspondence to distinct spatial regions.

ImageDimPlot(seurat_CRC2, size=.5)
Warning: No FOV associated with assay 'SCT', using global default FOV

As before, now we can use Seurat differential expression functions to identify marker genes for specific cell clusters.

FindMarkers: Identifies genes that are differentially expressed in a specified cluster compared to all other cells. seurat: The Seurat object containing the gene expression data and cluster identities. ident.1 = “0”: Specifies the cluster of interest for which marker genes are to be identified. In this case, cluster “0”. max.cells.per.ident = 500: Limits the number of cells to be used from each cluster for the differential expression analysis to 500. This can help to speed up the computation.

markers <- FindMarkers(seurat_CRC2, ident.1="0", max.cells.per.ident=500)

We can visualise expression of cluster specific markers using feature plots

ImageFeaturePlot(seurat_CRC2, "CD3E", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MS4A1", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "CEACAM5", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "KIT", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Or, as in our sequencing ST tutorial, detect and visualise top markers for every cluster.

markers <- FindAllMarkers(seurat_CRC2, max.cells.per.ident = 500)
Calculating cluster 0
Calculating cluster 1
Calculating cluster 2
Calculating cluster 3
Calculating cluster 4
Calculating cluster 5

scCustomize package provides a convenient helper function, Extract_Top_Markers, to extract the top marker genes for each cluster from the output of FindAllMarkers. This function simplifies the process of identifying and retrieving the most significant marker genes for analysis and visualisation.

In this case, we are extracting the top five markers per cluster.

top
 [1] "DMBT1"    "REG4"     "MLPH"     "FERMT1"   "EGFR"     "THBS1"    "CPE"     
 [8] "DEPP1"    "CES1"     "RUNX1T1"  "APOE"     "C1QB"     "C1QC"     "C1QA"    
[15] "CD14"     "MS4A1"    "CD7"      "CTLA4"    "GZMA"     "TRBC2"    "MUC12"   
[22] "CEACAM5"  "OLFM4"    "CEACAM6"  "CD177"    "DERL3"    "CPA3"     "TNFRSF17"
[29] "SLC18A2"  "MS4A2"   

Clustered_DotPlot function from the scCustomize package provides a convenient and visually appealing way to display expression patterns of top marker genes across clusters using a dot plot. This function not only plots the expression data but also clusters the genes and groups for enhanced visual interpretation. This is an alternative to Seurat DotPlot function.

k = 18: Determines the number of clusters for the hierarchical clustering of genes to enhance visual separation of expression patterns.

We can see that most clusters have unique markers, which suggests the dataset is not over-clustered.

Clustered_DotPlot(seurat_CRC2, features = top, k=18)
[[1]]

[[2]]

Additional Spatial Visualisations

The resolution of in situ datasets is typically very high and so it can be difficult to visualise everything in one plot. Below, we will explore different visualisations that can help unpick and understand the data a bit better.

To better visualise spatial distribution of clusters, sometimes it can be useful to subset only certain groups to reduce crowding. Here, we specifically only visualising two selected clusters.

WhichCells: Identifies cells based on specified criteria. seurat: The Seurat object. expression = seurat_clusters %in% c(0, 5): Logical expression to select cells belonging to clusters 0 and 5.

This works with ImageFeaturePlot too. Try it with some genes!

ImageDimPlot(seurat_CRC2, cells=WhichCells(seurat_CRC2, expression = seurat_clusters %in% c(0, 5)))
Warning: No FOV associated with assay 'SCT', using global default FOV

Sometimes, it can be useful to create additional fields of view of the data - for example, zooms of specific regions. First, let’s look at the coordinate system by plotting the data and turning on the plotting of the axes, which are off by default to create nicer looking plots.

This gives us a rough idea on where in the coordinate system to create any subsets or zooms of the data.

For example, if we want to zoom in on the follicle in the top right corner, we can see that it lies roughly between 4000-5000 and 8000-9000 coordinate regions.

ImageDimPlot(seurat_CRC2, axes = T)
Warning: No FOV associated with assay 'SCT', using global default FOV

So, let’s create a new FOV with these coordinates. For this, we can use the Crop function.

seurat[[“COLON”]]: The spatial assay to be cropped. x = c(4200, 5000): The x-axis range for the crop. y = c(8000, 8800): The y-axis range for the crop. coords = “plot”: Specifies the coordinate system to use (typically “plot” for spatial coordinates).

seurat[[“ROI1”]] <- cropped: Adds the cropped region as a new FOV named “ROI1” in the Seurat object. This could be a more informative name, but avoid using underscores!

cropped <- Crop(seurat[["COLON"]], x = c(4200, 5000), y = c(8000, 8800), coords = "plot")
seurat[["ROI1"]] <- cropped

Now we can limit our visualisations just to this region by specifying the name of the new FOV as an “fov” arguement.

As we are zooming in closer to the tissue, we can also switch from plotting cell centroids (i.e. dots) by default to visualising cell segmentation boundaries. Plotting cell boundary polygons for large FOVs can be quite time consuming, and doesn’t provide much more detail on a fully zoomed-out view.

ImageDimPlot(seurat, fov="ROI1", boundaries="segmentation", border.color = "black" )

We can visualise gene expression or other continous variable on the new FOV as before.

For example, here we have MS4A1/CD20 expression, which is a B-Cell marker. We can see it quite nicely limited to the lymphoid follicle.

ImageFeaturePlot(seurat, "MS4A1", fov="ROI1", boundaries="segmentation" , border.color = "black") + scale_fill_viridis_c()

We can also overlay the coordinates of individual molecules to the plot. For example, here we are added some more T-cell and B-cell specific markers.

This visualisation can be useful because molecules are stored independently of cells and cell boundaries in Seurat. Therefore, if there are regions where cell segmentation is not good, or if cells were filtered out from clustering analysis due to their low quality, the molecules will remain and can still be visualised this way.

For example, here we can see there are a few molecules of CXCR5 detected outside of cellular boundaries.

ImageFeaturePlot(seurat, "MS4A1", fov="ROI1", boundaries="segmentation", molecules=c("CXCR5", "FOXP3"), mols.size = .5, border.color = "black" ) + scale_fill_viridis_c()

Cell Type Identification

You can manually annotate your cell clusters, or you can classify them using a reference single-cell dataset. This process is simpler than for Visium data because our data is at the single-cell level, establishing a one-to-one relationship without the need for spot deconvolution.

However, our transcriptome is more limited here, and some cell types may not be well represented. Additionally, our single-cell reference might be missing some cell types that are not well captured by droplet-based technologies but are present in our tissue data.

In this example, we will use a single-cell reference dataset that we prepared earlier.

We will start by reading in the seurat RDS file.

ref <- readRDS("/project/shared/spatial_data_camp/datasets/SINGLE_CELL_REFERENCES/COLON_HC_5K_CELLS.RDS")

Examine the object:

ref
An object of class Seurat 
33556 features across 5725 samples within 3 assays 
Active assay: RNA (33538 features, 2000 variable features)
 3 layers present: counts, data, scale.data
 2 other assays present: HTO, ADT
 2 dimensional reductions calculated: pca, umap

And plot the pre-computed cell clusters. We can see that here we have quite high level annotation.

We want to evaluate how much structural information is lost in single-cell data when limiting ourselves to the targeted gene set. Accurate cluster prediction is challenging if the current gene set does not adequately identify them. To do this, we will quickly re-embedd the data using only the genes present in our spatial transcriptomics data and keep the original cluster annotations derived from unbiased data.

In this example, we can observe that the limited gene set does a reasonably good job at distinguishing major cell populations. However, it struggles to differentiate between similar cell types, such as myofibroblasts and fibroblasts, as effectively as before.

If we visualise the specificity of the gene panel across our single cell reference clusters, we can see that the panel coverage is mainly concentrated across epithelial cells and T-Cells and other immune cells, with few specific markers expressed by stromal cells.

Next, we can use the standard Seurat integration and cross-classification workflow to transfer single-cell derived labels to our spatial object.

Briefly, the first function identifies anchors between the reference single-cell dataset (ref) and the query spatial dataset (seurat). Anchors are pairs of cells that are considered similar between the datasets. The normalization.method = “SCT” specifies that SCTransform normalization should be used.

The second step transfers the cell type labels from the reference dataset to the query dataset. The anchorset argument specifies the anchors found in the previous step. The refdata = ref$CellType argument specifies the cell type labels from the reference dataset to be transferred. The prediction.assay = TRUE argument indicates that the transferred labels should be stored in a new assay in the query dataset. The weight.reduction = seurat[[“pca”]] argument specifies the dimensionality reduction to be used for weighting the transfer, and dims = 1:30 specifies the number of dimensions to use.

seurat_CRC2 <- TransferData(anchorset = anchors, 
                       refdata = ref$CellType, 
                       prediction.assay = TRUE,
                       weight.reduction = seurat_CRC2[["pca"]], 
                       query = seurat_CRC2, 
                       dims=1:30)
Finding integration vectors
Finding integration vector weights
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Predicting cell labels
Warning: Layer counts isn't present in the assay object; returning NULL

Unfortunately, the predicted labels and spatial clusters do not correspond clearly in all cases. This discrepancy is particularly evident in the middle regions of the UMAP, where many cells are predicted as epithelial cells - probably incorrectly!

How to improve this?

Ensure Good Representation of Cell Type Markers in in situ Target Panel Most critically, before undertaking any experiments you want to ensure that there is good representation of all cell types in your target panel - in this case, there is not much to be done as the data has already been generated.

Review and Refine Reference Data: Ensure that the reference single-cell dataset is comprehensive and accurately annotated. If certain cell types are not well represented or annotated in the reference dataset, it can lead to misclassification.

Increase the Number of Dimensions: Increasing the number of dimensions used in the UMAP and PCA steps might capture more variance in the data, leading to better label transfer.

Filter and Preprocess Data: Filtering out low-quality cells or genes and performing additional preprocessing steps can enhance the accuracy of the transfer anchors and, consequently, the label predictions.

Manually Annotate or Correct Predictions: In cases where automatic label transfer is insufficient, consider manually annotating or correcting the predictions for critical regions to ensure accuracy.

As before, we can also visualise the predicted cell labels in tissue space.

ImageDimPlot(seurat_CRC2, group.by = "predicted.id")
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOV

In line with non-specific predictions, we can also see that the prediction score across these areas is lower.

Outside of stromal cells, we can also see that prediction probability can be low in cells that embedd “between” clusters, for example between core T-Cells and B-Cells, two populations that should be distinct.

This is often the case where cell segmentation is imperfect and partitions transcripts in such a way that it generates “artificial” doublets by pulling in transcripts from an adjacent cell.

FeaturePlot(seurat, "predicted.id.score")

For example, if we visualise the lineage markers for T-Cells and B-Cells, we can see that they are often “co-expressed” in the same cells when biologically, they should not be.

The FeatureScatter function in Seurat is used to create a scatter plot showing the relationship between the expression levels of two genes across all cells. This visualization helps to identify potential correlations or patterns between the two genes.

ImageDimPlot(seurat_CRC2,  boundaries="segmentation", border.color = "black" )
Warning: No FOV associated with assay 'SCT', using global default FOV

ImageDimPlot(seurat_CRC2)
Warning: No FOV associated with assay 'SCT', using global default FOV

ImageDimPlot(seurat_CRC2, group.by = "predicted.id")
Warning: No FOV associated with assay 'SCT', using global default FOV

Spatial Neighbourhood Analyis

neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 20, return.neighbor=TRUE)
Computing nearest neighbors

Computing nearest neighbors

ImageDimPlot(seurat_CRC2)
Warning: No FOV associated with assay 'SCT', using global default FOV

Finding Spatially Correlated Genes

neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 50)
Computing nearest neighbor graph
Computing SNN
neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 50)
Computing nearest neighbor graph
Computing SNN
mt <- LayerData(seurat_CRC2, layer = "counts", assay = "XENIUM")
sum_mtx <- as.matrix(neighbours$nn %*% t(mt))

We can store the neighbourhood-aggregated values in our Seurat object as a separate assay, which we will call “NEIGHBOURHOOD50”. We then normalise the matrix.

seurat_CRC2 <- NormalizeData(seurat_CRC2, assay = "NEIGHBOURHOOD50")
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|

We can then apply quick correlation calculations to identify spatially correlated features.

modules <- cutree(heatmap$tree_row, 5)
modules
  AKR7A3    ANXA1     APOE    BANK1     C1QA     C1QB    C1QBP     C1QC      CA2     CCL5 
       1        2        3        4        3        3        1        3        1        2 
    CD14    CD163      CD2     CD24     CD3D     CD3E     CD3G      CD5      CD6    CD79A 
       3        3        2        1        2        2        2        2        2        2 
   CD79B     CD8A    CDCA7  CEACAM5  CEACAM6     CES1      CLU     CMBL      CPE  CREB3L1 
       4        2        1        5        5        2        2        1        4        5 
    CST7    CTLA4     CTSB    CXCR4     CYBB    DEPP1    DERL3   DPYSL3     EGFR    EPHB3 
       2        2        3        2        2        2        4        2        1        1 
    ETS1   FERMT1   FKBP11    FOXP3     FRZB     FYB1     FZD7   GALNT5    GATA2   GIMAP7 
       2        1        4        2        2        2        2        5        1        2 
  GPR183     GZMA     GZMK    HMGB2   IFITM1   IGFBP7   IL17RB     IL7R   IMPDH2      ITK 
       2        2        2        1        2        2        1        2        1        2 
   KLRB1  KRTCAP3     LGR5     LRMP      LTB      MAF    MKI67     MLPH    MS4A1    MS4A7 
       2        1        1        2        2        2        1        1        4        3 
   MYH14     NKG7      PBK    PCLAF    PLPP2   PLXND1  PPP1R1B    PTTG1     REG4   RNASE1 
       5        4        1        1        1        2        1        1        1        3 
   RNF43    ROBO1     RORA   RPS4Y1     RRM2  RUNX1T1    S100P   SEC11C  SELENOM SERPINA1 
       1        2        2        2        1        2        1        4        2        3 
   SFXN1  SLC12A2    SMOC2    SOCS3     SOX9   SPOCK2    STMN1    THBS1    TIGIT    TIMP3 
       1        1        1        2        1        2        1        2        2        2 
     TK1      TKT  TNFAIP3 TNFRSF17 TNFSF13B     TRAC    TRBC1    TRBC2     TYMS    UBE2C 
       1        1        2        2        2        2        2        2        1        1 
    VCAN 
       2 

Lets visualize some of the detected spatially co-localizing genes. For example, module 2 genes - we can see that CEACAM6 and AQP8 are spatially similar, but not necessarily always expressed by the same cells.

ImageFeaturePlot(seurat_CRC2, "CD24") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "ANXA1") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

seurat_CRC2 <- AddModuleScore(seurat_CRC2, features=split(names(modules), modules), assay = "SCT", nbin=3, name = "MOD" )

Visualising module scores - we can see that we have identified a group of genes co-localising at the base of the epithelial crypts (MOD1) and another module of genes co-localising in lymphoid follicles.

ImageFeaturePlot(seurat_CRC2, "MOD1") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD2") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD3") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD4") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD5") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Detecting Cellular Niches

neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 100)
Computing nearest neighbor graph
Computing SNN
diag(neighbours$nn) <- 0 # dont count transcriptome of the cell itself, just neighbours
mt <- LayerData(seurat_CRC2, layer = "counts", assay = "XENIUM")
sum_mtx <- as.matrix(neighbours$nn %*% t(mt))

How is this useful? Well, now you can cluster cells not on their gene expression values, but gene expression values of surrounding cells. This effectively partitions cells not based on their identity, but on their micro-environment! Using this approach, you can identify tissue niches

Alternative approaches - you could count cell types rather than gene expression values, but that requires you to have finalised cell annotation for your dataset, which is not ideal. So, we do unbiased transcriptomics approach.

How would you run this with cell types?

seurat_CRC2[["NEIGHBOURHOOD100"]] <- CreateAssayObject(t(sum_mtx))
DefaultAssay(seurat_CRC2) <- "NEIGHBOURHOOD100"
seurat_CRC2 <- NormalizeData(seurat_CRC2)
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
seurat_CRC2 <- ScaleData(seurat_CRC2, features = rownames(seurat_CRC2))
Centering and scaling data matrix

  |                                                                                        
  |                                                                                  |   0%
  |                                                                                        
  |==================================================================================| 100%
seurat_CRC2 <- RunPCA(seurat_CRC2, features = rownames(seurat_CRC2))
PC_ 1 
Positive:  C1QBP, HMGB2, CMBL, PPP1R1B, TYMS, SLC12A2, STMN1, KRTCAP3, CDCA7, FERMT1 
       CD24, TK1, PCLAF, CA2, IMPDH2, PLPP2, MLPH, EGFR, EPHB3, SOX9 
       RRM2, S100P, SFXN1, UBE2C, REG4, MKI67, RNF43, IL17RB, PBK, SMOC2 
Negative:  RPS4Y1, DPYSL3, SELENOM, ETS1, MAF, RORA, TRAC, ROBO1, GIMAP7, ANXA1 
       SPOCK2, CD79A, RUNX1T1, CD3E, CD2, TRBC2, IFITM1, DEPP1, THBS1, CD3G 
       IL7R, FYB1, VCAN, IGFBP7, CTLA4, GPR183, CD3D, TNFAIP3, GZMK, CXCR4 
PC_ 2 
Positive:  APOE, CEACAM5, C1QB, CTSB, CCL4, CEACAM6, SERPINA1, C1QA, C1QC, CYBB 
       RNASE1, MS4A7, FABP2, CD14, CD83, CEACAM1, CCL5, GZMA, TNFSF13B, CD163 
       MYH14, NKG7, CD8A, FYB1, IL1B, COL17A1, CDK6, RETNLB, RHOV, TBC1D4 
Negative:  MEIS2, NOVA1, ALDH1B1, CLU, AQP1, AGTR1, ID2, GNA11, FZD7, SLC6A8 
       IMPDH2, CES1, PDE4C, SEC11C, ADH1C, LYVE1, MAOB, EGFR, WFDC2, RGMB 
       CTSG, KIT, ANK2, PRDX4, ROBO2, EBPL, TKT, SELENOK, DERL3, PRPH 
PC_ 3 
Positive:  ALDH1B1, GNA11, C1QC, RNASE1, MEIS2, CKAP4, C1QB, CD163, CTSB, C1QA 
       PROX1, RGMB, TUBA1A, CD14, CES1, AGTR1, SLC6A8, MAOB, CPE, MS4A7 
       KIT, FZD7, THBS1, WFDC2, ETV1, MS4A2, CPA3, RUNX1T1, TIMP3, PLXND1 
Negative:  PAX5, CXCR5, FCRL1, MS4A1, SPIB, TCL1A, BANK1, CHI3L2, FCER2, FCRLA 
       CCR7, IRF8, SELL, CD40LG, CD83, TRAT1, VPREB3, CD79B, SMIM14, LTB 
       IER5, COL19A1, GZMK, MKI67, LGALS2, CXCR4, BATF, PTTG1, DNASE1L3, CD6 
PC_ 4 
Positive:  INSM1, ASCL2, TIMP3, IGFBP7, EPHB3, GATA2, THBS1, TUBB, VCAN, MUC12 
       PRDX4, LEF1, CDCA7, ETV1, CPE, PCLAF, FRZB, REG4, HES6, MAOB 
       RRM2, SCG2, LGR5, IFITM1, FERMT1, CD24, CA2, ROBO1, DPYSL3, AFAP1L2 
Negative:  CES2, SDCBP2, SMIM14, COL17A1, SLPI, CDHR5, HHLA2, SLC6A8, DMBT1, HDC 
       FABP2, ANXA13, TFF1, AREG, GPRC5C, PDE4C, BCAS1, RHOV, SULT1B1, GNA11 
       RNASE1, GALNT8, CEACAM6, MYH14, ODF2L, MEIS2, CFTR, UGT2B17, PDZK1IP1, DUOX2 
PC_ 5 
Positive:  PRDX4, LGALS2, CTSB, RNASE1, ID2, CA2, GNLY, CCL4, IL1B, NKG7 
       CD163, CES2, SLPI, KLRC2, C1QC, SERPINA1, TUBA1A, AKR7A3, PLCE1, AQP1 
       GPRIN3, GNA11, CDHR5, CXCL3, GZMA, EBPL, C1QA, CCL5, MLPH, PSTPIP2 
Negative:  FOXA3, MAOB, LEFTY1, PROX1, MUC12, RAB26, CDK6, HEPACAM2, RETNLB, KLK1 
       WFDC2, SMIM14, OLFM4, ATOH1, HES6, RGMB, INSM1, CEACAM6, CRYBA2, CHGB 
       SCNN1A, MB, CEACAM1, ASCL2, MS4A8, FCER2, RFX6, CHGA, CEACAM5, TNFRSF25 
seurat_CRC2 <- FindNeighbors(seurat_CRC2, reduction = "pca", dims = 1:10)
Computing nearest neighbor graph
Computing SNN
seurat_CRC2 <- FindClusters(seurat_CRC2, resolution = 0.1, cluster.name = "Niches")
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 73985
Number of edges: 1833699

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9640
Number of communities: 9
Elapsed time: 10 seconds

Lets visualise the detected “niches”. We can see that we have achieved a coarse partioning of the cells into crypt top, mid-crypt and crypt-base regions, as well as segmenting out follicles and sub-mucosal stroma.

How would you tweak the above approach to generate more or less granular niches?

ImageDimPlot(seurat_CRC2, group.by = "Niches")
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOV

We can tabulate our detected niches with predicted cell type labels (or clusters) to visualise enrichment of different cell types across spatial niches.

For example, as could be expected, T-Cells and B-Cells enrich in Niche 2 (follicular).

saveRDS(seurat_CRC2, "seurat_CRC2_annot.RDS")

change the color

ImageDimPlot(seurat_CRC2, cols = cell_colours, group.by = "predicted.id")
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOV

merged <- readRDS("CRC_merge")
Warning: cannot open compressed file 'CRC_merge', probable reason 'No such file or directory'Error in gzfile(file, "rb") : cannot open the connection
LS0tCnRpdGxlOiAiSHVtYW4gQ29sb24gWGVuaXVtIGluIHNpdHUgU1QgRGF0YXNldCwgTnVjbGVpIFNlZ21lbnRhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCkZpcnN0IHdlIG5lZWQgdG8gc2V0IHVwIHRoZSBlbnZpcm9ubWVudCBhbmQgbG9hZCB0aGUgcGFja2FnZXMgd2Ugd2lsbCB1c2UgZm9yIHRoaXMgd29ya3Nob3AuIAoKKmxpYnJhcnkoU2V1cmF0KSo6IExvYWRzIHRoZSBTZXVyYXQgcGFja2FnZSwgd2hpY2ggaXMgYSBjb21wcmVoZW5zaXZlIHRvb2xraXQgZm9yIHNpbmdsZS1jZWxsIFJOQSBzZXF1ZW5jaW5nIGFuZCBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGFuYWx5c2lzLiBJdCBwcm92aWRlcyBhIHdpZGUgcmFuZ2Ugb2YgZnVuY3Rpb25zIGZvciBkYXRhIHByZXByb2Nlc3NpbmcsIG5vcm1hbGl6YXRpb24sIGNsdXN0ZXJpbmcsIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgYW5kIHZpc3VhbGl6YXRpb24uIEV4cGxvcmUgZG9jdW1lbnRhdGlvbiBoZXJlOiBodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0LwoKKmxpYnJhcnkoZ2dwbG90MikqOiBMb2FkcyB0aGUgZ2dwbG90MiBwYWNrYWdlLCBhIHBvd2VyZnVsIGFuZCBmbGV4aWJsZSBzeXN0ZW0gZm9yIGNyZWF0aW5nIHN0YXRpYyB2aXN1YWxpemF0aW9ucyBpbiBSLiBFeHBsb3JlIGRvY3VtZW50YXRpb24gaGVyZTogaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvCgoqbGlicmFyeShzY0N1c3RvbWl6ZSkqOiBMb2FkcyB0aGUgc2NDdXN0b21pemUgcGFja2FnZSwgd2hpY2ggcHJvdmlkZXMgY3VzdG9tIGZ1bmN0aW9ucyBhbmQgdGhlbWVzIHRvIGVuaGFuY2UgdGhlIHZpc3VhbGl6YXRpb24gYW5kIGFuYWx5c2lzIGNhcGFiaWxpdGllcyBvZiBzaW5nbGUtY2VsbCBhbmQgc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YSwgb2Z0ZW4gaW4gY29uanVuY3Rpb24gd2l0aCBTZXVyYXQuIEV4cGxvcmUgZG9jdW1lbnRhdGlvbiBoZXJlOiBodHRwczovL3NhbXVlbC1tYXJzaC5naXRodWIuaW8vc2NDdXN0b21pemUvCgoqbGlicmFyeShyZWFkcikqOiBMb2FkcyByZWFkciBwYWNrYWdlIGZvciBmYXN0IGFuZCBmcmllbmRseSByZWFkaW5nIG9mIHJlY3Rhbmd1bGFyIGRhdGEsIHN1Y2ggYXMgQ1NWIGZpbGVzLCBpbnRvIFIuCgoqbGlicmFyeShwaGVhdG1hcCkqOiBMb2FkcyBwaGVhdG1hcCBwYWNrYWdlLCB3aGljaCBpcyBmb3IgY3JlYXRpbmcgcHJldHR5IGhlYXRtYXBzLCBvZmZlcmluZyBiZXR0ZXIgY29udHJvbCBvdmVyIGhlYXRtYXAgY3VzdG9taXphdGlvbiBjb21wYXJlZCB0byBiYXNlIFIuCgoqbGlicmFyeShtYXRyaXhTdGF0cykqOiBtYXRyaXhTdGF0cyBwcm92aWRlcyBoaWdobHkgb3B0aW1pemVkIGZ1bmN0aW9ucyBmb3IgbWF0cml4IG9wZXJhdGlvbnMsIHBhcnRpY3VsYXJseSB1c2VmdWwgZm9yIGNvbXB1dGluZyByb3cgYW5kIGNvbHVtbiBzdW1tYXJpZXMuIAoKKmxpYnJhcnkoc3BkZXApKjogc3BkZXAgc3RhbmRzIGZvciBTcGF0aWFsIERlcGVuZGVuY2UgYW5kIFNwYXRpYWwgQXV0b2NvcnJlbGF0aW9uLCBhbmQgaXQgcHJvdmlkZXMgZnVuY3Rpb25zIGZvciBzcGF0aWFsIGRhdGEgYW5hbHlzaXMsIGluY2x1ZGluZyBzcGF0aWFsIHdlaWdodHMgZ2VuZXJhdGlvbiwgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gc3RhdGlzdGljcywgYW5kIHNwYXRpYWwgcmVncmVzc2lvbi4KCipsaWJyYXJ5KGdlb2pzb25SKSogVGhlIGdlb2pzb25SIGxpYnJhcnkgaXMgdXNlZCBmb3IgaGFuZGxpbmcgR2VvSlNPTiBkYXRhIGluIFIuIEdlb0pTT04gaXMgYSBmb3JtYXQgZm9yIGVuY29kaW5nIGEgdmFyaWV0eSBvZiBnZW9ncmFwaGljIGRhdGEgc3RydWN0dXJlcyB1c2luZyBKYXZhU2NyaXB0IE9iamVjdCBOb3RhdGlvbiAoSlNPTikuIEl0IGlzIHNvbWV0aW1lcyB1c2VkIGFzIGEgZm9ybWF0IGZvciBzdG9yaW5nIGNlbGwgc2VnbWVudGF0aW9uIGJvdW5kYXJpZXMuCgpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY0N1c3RvbWl6ZSkKbGlicmFyeShyZWFkcikKbGlicmFyeShwaGVhdG1hcCkKbGlicmFyeShtYXRyaXhTdGF0cykKbGlicmFyeShzcGRlcCkKbGlicmFyeShnZW9qc29uUikKYGBgClNldHMgdGhlIHBhdGggdG8gdGhlIGRpcmVjdG9yeSBjb250YWluaW5nIHRoZSBYZW5pdW0gb3V0cHV0IGRhdGEgLSB0aGlzIGlzIHRoZSBkaXJlY3Rvcnkgd2hlcmUgYWxsIG9mIHRoZSBvdXRwdXRzIGFyZSBzdG9yZWQuCmBgYHtyfQpkYXRhX2RpciA8LSAiL3Byb2plY3Qvc2hhcmVkL3NwYXRpYWxfZGF0YV9jYW1wL2RhdGFzZXRzL0RBVEFTRVQyL1hFTklVTV9DT0xPUkVDVEFMX0NBTkNFUi8iCmBgYAoKKlJlYWRYZW5pdW0qIHJlYWRzIFhlbml1bSBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGZyb20gYSBzcGVjaWZpZWQgZGlyZWN0b3J5IHVzaW5nIGEgU2V1cmF0IHdyYXBwZXIgZnVuY3Rpb24gdGhhdCBzdXBwb3J0cyB0aGlzIGRhdGEgZm9ybWF0LiBYZW5pdW0gZGF0YSB0eXBpY2FsbHkgaW5jbHVkZXMgZXhwcmVzc2lvbiBtYXRyaWNlcyBhbmQgc3BhdGlhbCBjb29yZGluYXRlcywgYWxvbmcgd2l0aCBvdGhlciAgaW5mb3JtYXRpb24gYWJvdXQgY2VsbCBjZW50cm9pZHMgYW5kIHNlZ21lbnRhdGlvbnMgYW5kIGNvb3JkaW5hdGVzIG9mIGluZGl2aWR1YWwgdHJhbnNjcmlwdHMuIAoKKmRhdGFfZGlyKjogVGhlIHBhdGggdG8gdGhlIGRpcmVjdG9yeSBjb250YWluaW5nIHRoZSBYZW5pdW0gZGF0YSwgc2V0IGluIHRoZSBwcmV2aW91cyBzdGVwLgoqb3V0cyA9IGMoIm1hdHJpeCIsICJtaWNyb25zIikqOiBTcGVjaWZpZXMgdGhlIG91dHB1dHMgdG8gcmVhZCBmcm9tIHRoZSBkYXRhIGRpcmVjdG9yeS4gbWF0cml4IHJlZmVycyB0byBzdW1tYXJpc2VkIGNlbGwgYnkgZ2VuZSBtYXRyaXggYW5kIG1pY3JvbnMgcmVmZXJzIHRvIGluZGl2aWR1YWwgdHJhbnNjcmlwdCBjb29yZGluYXRlcy4KCip0eXBlID0gYygiY2VudHJvaWRzIiwgInNlZ21lbnRhdGlvbnMiKSo6IEluZGljYXRlcyB0aGUgdHlwZXMgb2Ygc3BhdGlhbCBpbmZvcm1hdGlvbiB0byBpbmNsdWRlIC0gaGVyZSwgd2UgYXJlIHJlYWRpbmcgaWIgYm90aCBjZWxsIGNlbnRyb2lkIGNvb3JkaW5hdGVzIGFuZCBjZWxsIGJvdW5kYXJ5IHNlZ21lbnRhdGlvbnMuCgoKYGBge3J9CmRhdGEgPC0gUmVhZFhlbml1bShkYXRhX2Rpciwgb3V0cyA9IGMoIm1hdHJpeCIsICJtaWNyb25zIiksIHR5cGU9YygiY2VudHJvaWRzIiwgInNlZ21lbnRhdGlvbnMiKSkKYGBgClRoaXMgcHJvdmlkZXMgdXMgYSBsaXN0IG9mIGRhdGE6CmBgYHtyfQpuYW1lcyhkYXRhKQpgYGAKTWF0cml4IGlzIGZ1cnRoZXIgc3BsaXQgaW50byBnZW5lIGV4cHJlc3Npb24gbWF0cml4IGFuZCB2YXJpb3VzIGNvbnRyb2wgcHJvYmVzIGFuZCBjb2Rld29yZHMuIERpZmZlcmVudCBwbGF0Zm9ybXMgYW5kIHBsYXRmb3JtIHZlcnNpb25zIGluY2x1ZGUgZGlmZmVyZW50IGNvbnRyb2wgcHJvYmVzLiBBcyB0aGlzIHdpbGwgdmFyeSwgaXQncyBpbXBvcnRhbnQgdG8gY2hlY2sgYW5kIHVuZGVyc3RhbmQgd2hhdCB0aGUgc3BlY2lmaWMgY29udHJvbHMgaW4geW91ciBvd24gZGF0YSBhcmUuICAKCkhlcmUsIG5lZ2F0aXZlIGNvbnRyb2wgcHJvYmVzIGFyZSBwcm9iZXMgdGhhdCBhcmUgYWRkZWQgdG8gdGhlIHJlYWN0aW9uIGJ1dCB0YXJnZXQgbm9uLWJpb2xvZ2ljYWwgc2VxdWVuY2VzIGFuZCBzaG91bGQgbm90IGJpbmQgYW55IHRpc3N1ZSBSTkEuIE5lZ2F0aXZlIGNvbnRyb2wgY29kZXdvcmRzIGFyZSB2YWxpZCBjb2Rld29yZHMsIGJ1dCBubyBwcm9iZXMgd2l0aCB0aGF0IGNvZGV3b3JkIGFkZGVkIHRvIHRoZSByZWFjdGlvbi4gVGhpcyBlZmZlY3RpdmVseSB0ZWxscyB1cyBob3cgZ29vZCB0aGUgdHJhbnNjcmlwdCBjYWxsaW5nIGFsZ29yaXRobSBpcy4KCmBgYHtyfQpuYW1lcyhkYXRhJG1hdHJpeCkKYGBgClJlYWQgaW4gYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY2VsbHMgLSB0aGlzIGdpdmVzIHVzIHByZS1jYWxjdWxhdGVkIGluZm9ybWF0aW9uLCBmb3IgZXhhbXBsZSBzZWdtZW50ZWQgY2VsbCBvciBudWNsZXVzIHNpemUgZm9yIGVhY2ggY2VsbC4KYGBge3J9CmNlbGxfbWV0YV9kYXRhIDwtIHJlYWQuY3N2KGZpbGUucGF0aChkYXRhX2RpciwgImNlbGxzLmNzdi5neiIpKQpyb3duYW1lcyhjZWxsX21ldGFfZGF0YSkgPC0gY2VsbF9tZXRhX2RhdGEkY2VsbF9pZApoZWFkKGNlbGxfbWV0YV9kYXRhKQpgYGAKCldlIHdpbGwgc3RhcnQgYnkgY3JlYXRpbmcgYSBiYXNpYyBzZXVyYXQgb2JqZWN0IGZyb20gdGhlIGRhdGEuIAoKKkNyZWF0ZVNldXJhdE9iamVjdCogZnVuY3Rpb24gaW5pdGlhbGl6ZXMgYSBTZXVyYXQgb2JqZWN0IHVzaW5nIHRoZSBwcm92aWRlZCBnZW5lIGV4cHJlc3Npb24gbWF0cml4IGFuZCBvcHRpb25hbCBtZXRhZGF0YS4KCipjb3VudHMqOiBUaGUgZ2VuZSBleHByZXNzaW9uIG1hdHJpeCwgd2hpY2ggY29udGFpbnMgdGhlIHJhdyBjb3VudCBkYXRhIGZvciBlYWNoIGdlbmUgaW4gZWFjaCBjZWxsLgoqZGF0YSRtYXRyaXhbWyJHZW5lIEV4cHJlc3Npb24iXV0qOiBTcGVjaWZpZXMgdGhlIGdlbmUgZXhwcmVzc2lvbiBtYXRyaXggZXh0cmFjdGVkIGZyb20gdGhlIGxvYWRlZCBYZW5pdW0gZGF0YS4gSGVyZSwgd2UgbGVhdmUgb3V0IHRoZSBjb250cm9sIHByb2JlcyBmb3Igbm93LiAKCiphc3NheSo6IFRoZSBuYW1lIG9mIHRoZSBhc3NheSAtIHlvdSBjYW4gY2FsbCBpdCBhbnl0aGluZyB5b3UgbGlrZS4gSGVyZSwgd2UgZ28gd2l0aCAiWEVOSVVNIi4gCgoqbWV0YS5kYXRhKjogTWV0YWRhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBjZWxscyBvciBzcG90cy4gSGVyZSwgd2UgYWRkIHRoZSBjZWxsIHN0YXRpc3RpY3Mgd2UgcmVhZCBpbiBlYXJsaWVyIGFzICpjZWxsX21ldGFfZGF0YSouCgpCeSBwcmludGluZyB0aGUgKnNldXJhdCogb2JqZWN0LCB3ZSBjYW4gc2VlIHRoYXQgd2UgcmVhZCBpbiB+IDMwLDAwMCBjZWxscyB3aXRoIG1lYXN1cmVzIGZvciAzMjUgZ2VuZXMKCmBgYHtyfQpzZXVyYXQgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IGRhdGEkbWF0cml4W1siR2VuZSBFeHByZXNzaW9uIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJYRU5JVU0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRhLmRhdGEgPSBjZWxsX21ldGFfZGF0YSkKc2V1cmF0CmBgYAoKQWRkaW5nIHNwYXRpYWwgY29vcmRpbmF0ZXMgdG8gYSBTZXVyYXQgb2JqZWN0IGFsbG93cyBmb3Igc3BhdGlhbGx5IHJlc29sdmVkIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uLiBUaGlzIHJlcXVpcmVzIGNyZWF0aW5nIG9iamVjdHMgZm9yIGNlbnRyb2lkcyBhbmQgc2VnbWVudGF0aW9ucyB3ZSByZWFkIGluIGVhcmxpZXIsIGFuZCB0aGVuIGludGVncmF0aW5nIHRoZXNlIHdpdGggdGhlIG1haW4gU2V1cmF0IG9iamVjdC4KCipDcmVhdGVGT1YqOiBUaGlzIGZ1bmN0aW9uIGNyZWF0ZXMgYSBmaWVsZCBvZiB2aWV3IChGT1YpIG9iamVjdCB0aGF0IGluY2x1ZGVzIHNwYXRpYWwgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGNlbnRyb2lkcywgc2VnbWVudGF0aW9ucywgYW5kIG1vbGVjdWxlIGNvb3JkaW5hdGVzLiBBbiBGT1YgY2FuIGJlIHRoZSBlbnRpcmUgc2xpZGUsIG9yIGEgc2VsZWN0ZWQgcmVnaW9uIHdpdGhpbiBhIHNsaWRlIC0gaS5lLiBpdCBkb2VzIG5vdCBuZWVkIHRvIGhhdmUgZW50cmllcyBmb3IgYWxsIHRoZSBjZWxscyBpbiB0aGUgc2V1cmF0IG9iamVjdC4KCipjb29yZHMqOiBBIGxpc3QgY29udGFpbmluZyB0aGUgY2VudHJvaWRzIGFuZC9vciBzZWdtZW50YXRpb24gZGF0YS4gRm9yIGxhcmdlciBkYXRhc2V0cywgaXQgY2FuIGJlIHF1aWNrZXIgdG8gb25seSBsb2FkIGNlbnRyb2lkcywgYXMgdGhpcyBtaW5pbWlzZXMgdGhlIGFtb3VudCBvZiBkYXRhIHBvaW50cy4gCgoqY2VudHJvaWRzID0gQ3JlYXRlQ2VudHJvaWRzKGRhdGEkY2VudHJvaWRzKSo6IENyZWF0ZXMgYSBjZW50cm9pZHMgb2JqZWN0IGZyb20gdGhlIGNlbnRyb2lkIGRhdGEgaW4gdGhlIFhlbml1bSBkYXRhc2V0Lgoqc2VnbWVudGF0aW9uID0gQ3JlYXRlU2VnbWVudGF0aW9uKGRhdGEkc2VnbWVudGF0aW9ucykqOiBDcmVhdGVzIGEgc2VnbWVudGF0aW9uIG9iamVjdCBmcm9tIHRoZSBzZWdtZW50YXRpb24gZGF0YSBpbiB0aGUgWGVuaXVtIGRhdGFzZXQuCgoqdHlwZSA9IGMoInNlZ21lbnRhdGlvbiIsICJjZW50cm9pZHMiKSo6IFNwZWNpZmllcyB0aGUgdHlwZXMgb2Ygc3BhdGlhbCBkYXRhIGJlaW5nIGluY2x1ZGVkLCB3aGljaCBhcmUgc2VnbWVudGF0aW9uIGFuZCBjZW50cm9pZCBkYXRhLgoKKm1vbGVjdWxlcyA9IGRhdGEkbWljcm9ucyo6IFRoZSBzcGF0aWFsIGNvb3JkaW5hdGVzIG9mIGluZGl2aWR1YWwgdHJhbnNjcmlwdHMvbW9sZWN1bGVzIGluIHRoZSBkYXRhLiBUaGlzIGlzIG9wdGlvbmFsIC0gZm9yIGxhcmdlciBkYXRhc2V0cywgc2tpcHBpbmcgdHJhbnNjcmlwdCBjb29yZGluYXRlcyBjYW4gYmUgYSBnb29kIGlkZWEuCgoqc2V1cmF0W1siQ09MT04iXV0gPC0gY29vcmRzKjogQWRkcyB0aGUgY3JlYXRlZCBGT1Ygb2JqZWN0IHRvIHRoZSBTZXVyYXQgb2JqZWN0IHVuZGVyIHRoZSBuZXcgRk9WIG5hbWUgIkNPTE9OIi4gVGhpcyBjYW4gYmUgbmFtZWQgKGFsbW9zdCkgYW55dGhpbmcgLSBidXQsIGF2b2lkIHVzaW5nIHVuZGVyc2NvcmVzIGFzIHRoaXMgY2FuIGNyZWF0ZSBzb21lIHVuZXhwZWN0ZWQgYmVoYXZpb3VycyBsYXRlci4KClRJUDogKkxvYWRYZW5pdW0oKSogaXMgYSB3cmFwcGVyIHRoYXQgd291bGQgbG9hZCBpbiBib3RoIGNlbGwgY291bnRzIG1hdHJpeCBhbmQgc3BhdGlhbCBjb29yZGluYXRlcyBpbiBvbmUgZnVuY3Rpb24sIHNpbXBsaWZ5aW5nIHRoZXNlIHN0ZXBzLiBIb3dldmVyLCAqaW4gc2l0dSogcGxhdGZvcm1zIGFyZSBldm9sdmluZyBhdCBhIHZlcnkgZmFzdCByYXRlIGFuZCB0aGVyZSBhcmUgY29uc3RhbnQgY2hhbmdlcyBvbiBob3cgdGhlIGRhdGEgaXMgc3RvcmVkLCBpbiBwYXJ0aWN1bGFyIGZvciBmaWxlIGZvcm1hdHMgZm9yIGNlbGwgc2VnbWVudGF0aW9uIGFuZCBjb29yZGluYXRlcy4gSGVyZSwgd2UgaGF2ZSBicm9rZW4gZG93biB0aGUgc3RlcHMgdG8gc2hvdyBob3cgdG8gYXNzZW1ibGUgYW4gaW4gc2l0dSBzZXVyYXQgb2JqZWN0IGZyb20gdGhlIGtleSBjb21wb25lbnRzLCBpbiBjYXNlIHRoZSBwbGF0Zm9ybSBzcGVjaWZpYyByZWFkZXJzIGRvbid0IHdvcmsgZm9yIHlvdXIgc3BlY2lmaWMgZGF0YS4KYGBge3J9CmNvb3JkcyA8LSBDcmVhdGVGT1YoY29vcmRzID0gbGlzdChjZW50cm9pZHMgPSBDcmVhdGVDZW50cm9pZHMoZGF0YSRjZW50cm9pZHMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZ21lbnRhdGlvbiA9IENyZWF0ZVNlZ21lbnRhdGlvbihkYXRhJHNlZ21lbnRhdGlvbnMpKSwKICAgICAgICAgICAgICAgICAgICB0eXBlID0gYygic2VnbWVudGF0aW9uIiwgImNlbnRyb2lkcyIpLAogICAgICAgICAgICAgICAgICAgIG1vbGVjdWxlcyA9IGRhdGEkbWljcm9ucywKICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJYRU5JVU0iKQpzZXVyYXRbWyJDT0xPTkMyIl1dIDwtIGNvb3JkcyAgCmBgYAoKSW5zcGVjdCB0aGUgb2JqZWN0IC0gbm93LCB5b3UgY2FuIHNlZSB3ZSBoYXZlIGFkZGVkIGEgc3BhdGlhbCBmaWVsZCBvZiB2aWV3OgoKVG8gc3Vic2V0IHRoZSBvYmplY3QKYGBge3J9CmNyb3BwZWQgPC0gQ3JvcChzZXVyYXRbWyJDT0xPTkMyIl1dLCB4ID0gYygxMDAwLCAzMDAwKSwgeSA9IGMoMzAwMCwgNjAwMCksIGNvb3JkcyA9ICJwbG90IikKc2V1cmF0W1siQ1JDMiJdXSA8LSBjcm9wcGVkCkltYWdlRGltUGxvdChzZXVyYXQsIGZvdj0gIkNSQzIiLGF4ZXMgPSBUKQpgYGAKQWRkaW5nIGNvbnRyb2wgcHJvYmVzIGFuZCBjb2Rld29yZHMgYXMgc2VwYXJhdGUgYXNzYXlzIGluIHRoZSBTZXVyYXQgb2JqZWN0IGFsbG93cyBmb3IgdGhlIHRyYWNraW5nIGFuZCBhbmFseXNpcyBvZiB0ZWNobmljYWwgYXJ0aWZhY3RzIGFuZCBub2lzZSB3aXRoaW4geW91ciBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhLCB3aGlsZSBrZWVwaW5nIHRoZXNlIG91dHB1dHMgc2VwYXJhdGUgZnJvbSB0aGUgbWFpbiBiaW9sb2dpY2FsIGdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMuCgoKKipVbmFzc2lnbmVkIGNvZGV3b3JkcyoqIGFyZSB1bnVzZWQgY29kZXdvcmRzLiBUaGVyZSBpcyBubyBwcm9iZSBpbiBhIHBhcnRpY3VsYXIgZ2VuZSBwYW5lbCB0aGF0IHdpbGwgZ2VuZXJhdGUgdGhlIGNvZGV3b3JkLgoKKipOZWdhdGl2ZSBjb250cm9sIHByb2JlcyoqIGFyZSBwcm9iZXMgdGhhdCBleGlzdCBpbiB0aGUgcGFuZWxzIGJ1dCB0YXJnZXQgbm9uLWJpb2xvZ2ljYWwgc2VxdWVuY2VzLiBUaGV5IGNhbiBiZSB1c2VkIHRvIGFzc2VzcyB0aGUgc3BlY2lmaWNpdHkgb2YgdGhlIGFzc2F5LgoKKipOZWdhdGl2ZSBjb250cm9sIGNvZGV3b3JkcyoqIGFyZSBjb2Rld29yZHMgaW4gdGhlIGNvZGVib29rIHRoYXQgZG8gbm90IGhhdmUgYW55IHByb2JlcyBtYXRjaGluZyB0aGF0IGNvZGUuIFRoZXkgYXJlIGNob3NlbiB0byBtZWV0IHRoZSBzYW1lIHJlcXVpcmVtZW50cyBhcyByZWd1bGFyIGNvZGV3b3JkcyBhbmQgY2FuIGJlIHVzZWQgdG8gYXNzZXNzIHRoZSBzcGVjaWZpY2l0eSBvZiB0aGUgZGVjb2RpbmcgYWxnb3JpdGhtLgoKCmBgYHtyfQpzZXVyYXRbWyJOZWdhdGl2ZS5Db250cm9sLkNvZGV3b3JkIl1dIDwtIENyZWF0ZUFzc2F5T2JqZWN0KGNvdW50cyA9IGRhdGEkbWF0cml4W1siTmVnYXRpdmUgQ29udHJvbCBDb2Rld29yZCJdXSkKc2V1cmF0W1siTmVnYXRpdmUuQ29udHJvbC5Qcm9iZSJdXSA8LSBDcmVhdGVBc3NheU9iamVjdChjb3VudHMgPSBkYXRhJG1hdHJpeFtbIk5lZ2F0aXZlIENvbnRyb2wgUHJvYmUiXV0pCnNldXJhdFtbIlVuYXNzaWduZWQuQ29kZXdvcmQiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QoY291bnRzID0gZGF0YSRtYXRyaXhbWyJVbmFzc2lnbmVkIENvZGV3b3JkIl1dKQpgYGAKCnN1YnNldCBhbiBvYmplY3QKYGBge3J9CiMgeCA9IGMoMTAwMCwgMzAwMCksIHkgPSBjKDMwMDAsIDYwMDApCnNldXJhdF9DUkMyIDwtIHNldXJhdFssIHNldXJhdCR4X2NlbnRyb2lkID49IDEwMDAgJiBzZXVyYXQkeF9jZW50cm9pZCA8PSAzMDAwICYgc2V1cmF0JHlfY2VudHJvaWQgPj0gMzAwMCAmIHNldXJhdCR5X2NlbnRyb2lkIDw9IDYwMDBdCnNldXJhdF9DUkMyICNyZWFkIHRoZSBvYmplY3QKc2V1cmF0ICNyZWFkIHRoZSBvYmplY3QKc2F2ZVJEUyhzZXVyYXRfQ1JDMiwgZmlsZT0iQ1JDMl9zdWJzZXQuUkRTIikKYGBgCkxldCdzIHN0YXJ0IHdpdGggc29tZSBiYXNpYyBRQyBhbmQgdmlzdWFsaXNhdGlvbiBvZiB0aGUgZGF0YS4gCgpJbiBTZXVyYXQsICppbiBzaXR1KiBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBjb3VudGVycGFydCBmdW5jdGlvbnMgdG8gKidTcGF0aWFsRGltUGxvdCcqIGFuZCAqJ1NwYXRpYWxGZWF0dXJlUGxvdCcqIHdlIGNvdmVyZWQgeWVzdGVyZGF5IGFyZSBjYWxsZWQgKidJbWFnZUZlYXR1cmVQbG90JyogYW5kIConSW1hZ2VEaW1QbG90JyouIFRoZXNlIGhhdmUgYWRkaXRpb25hbCBmdW5jdGlvbmFsaXR5IHRvIHBsb3QgY2VsbCBzZWdtZW50YXRpb25zIGFuZCBpbmRpdmlkdWFsIHRyYW5zY3JpcHQgY29vcmRpbmF0ZXMsIGJ1dCBvdGhlcndpc2UgZnVuY3Rpb24gZXhhY3RseSB0aGUgc2FtZSBhcyB0aGUgc2VxdWVuY2luZyBiYXNlZCBTVCBjb3VudGVycGFydHMuIAoKRmlyc3QsIGxldHMgdmlzdWFsaXNlIHRoZSB0b3RhbCB0cmFuc2NyaXB0cyBkZXRlY3RlZCBwZXIgY2VsbC4KCkFzIGluIHNjUk5BLVNlcSBkYXRhLCB0aGlzIGlzIHRoZSBtb3N0IGJhc2ljIG1lYXN1cmUgb2Ygb3ZlcmFsbCBzaWduYWwgYW5kIGhvdyB3ZWxsIHRoZSBkYXRhIGxvb2tzLiAKClVubGlrZSBpbiBzY1JOQS1TZXEgZGF0YSBvciB1bmJpYXNlZCBzZXF1ZW5jaW5nLWJhc2VkIFNULCB0aGVzZSBtZWFzdXJlcyBhcmUgYWxzbyB2ZXJ5IGhlYXZpbHkgZGVwZW5kZW50IG5vdCBvbmx5IG9uIHRoZSB0b3RhbCBSTkEgcXVhbnRpdHkgb2YgZWFjaCBjZWxsIGFuZCB0aXNzdWUgcXVhbGl0eSwgYnV0IGFsc28gb24gdGhlIHRhcmdldCBwYW5lbCB1c2VkIGZvciB0aGUgZXhwZXJpbWVudC4gVW5kZXItcmVwcmVzZW50ZWQgY2VsbCB0eXBlcyB3aWxsIG5hdHVyYWxseSB5aWVsZCBmZXdlciB0cmFuc2NyaXB0cy4KRmluYWxseSwgdGhlIHF1YWxpdHkgb2YgY2VsbCBzZWdtZW50YXRpb24gYWxzbyBwbGF5cyBhIHJvbGUuCgpJbiB0aGlzIGNhc2UsIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgYXJlYXMgd2l0aCBoaWdoZXIgYW5kIGxvd2VyIHRvdGFsIHRyYW5zY3JpcHRzIGRldGVjdGVkLiAKClVuZGVyc3RhbmRpbmcgeW91ciB0aXNzdWUgYW5kIHRhcmdldCBwYW5lbCBoZXJlIGlzIGltcG9ydGFudCB0byBkZWxpbmVhdGUgd2hlcmUgdGhlc2UgZGlmZmVyZW5jZXMgYXJlIGJpb2xvZ2ljYWwgYW5kIHdoZXJlIHRoZXkgbWF5IGJlIHRlY2huaWNhbC4KCmBgYHtyfQojeCA9IGMoMTAwMCwgMzAwMCksIHkgPSBjKDMwMDAsIDYwMDApLApgYGAKU2ltaWxhcmx5LCB3ZSBjYW4gdmlzdWFsaXNlIHRoZSB0b3RhbCBudW1iZXIgb2YgZ2VuZSBkZXRlY3RlZCBwZXIgY2VsbC4gWW91IGNhbiBzZWUgdGhhdCB0aGlzIGlzIGEgYml0IGxlc3MgdmFyaWFibGUgYWNyb3NzIHRpc3N1ZS4gCgpUaGlzIGNhbiBhbHNvIHN1Z2dlc3QgdGhhdCB0aGVyZSBjZWxscyBhdCB0aGUgdG9wIG9mIHRoZSBlcGl0aGVsaWFsIGNyeXB0cyBpbiB0aGlzIHNhbXBsZSB3aXRoIGdlbmVzIGRldGVjdGVkIGF0IGhpZ2ggY29weSBudW1iZXIgdGhhbiB0aGUgcmVzdCBvZiB0aGUgdGlzc3VlLgoKYGBge3J9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJuRmVhdHVyZV9YRU5JVU0iLCBheGVzID0gVCkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYAoKVGhpcyBjb2RlIGV4YW1pbmVzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG51bWJlciBvZiBmZWF0dXJlcyAoZ2VuZXMpIGRldGVjdGVkIHBlciBjZWxsIGluIHRoZSBTZXVyYXQgb2JqZWN0IHVzaW5nIGEgZGVuc2l0eSBwbG90IGFuZCBjYWxjdWxhdGVzIHNwZWNpZmljIHF1YW50aWxlcyBvZiB0aGlzIGRpc3RyaWJ1dGlvbi4gVGhpcyBpcyBpbXBvcnRhbnQgZm9yIHVuZGVyc3RhbmRpbmcgdGhlIHZhcmlhYmlsaXR5IGFuZCBkaXN0cmlidXRpb24gb2YgZGV0ZWN0ZWQgZmVhdHVyZXMsIHdoaWNoIGNhbiBoZWxwIGlkZW50aWZ5IHBvdGVudGlhbCBpc3N1ZXMgc3VjaCBhcyBsb3ctcXVhbGl0eSBjZWxscyBhbmQgZGV0ZXJtaW5lIGFueSBmaWx0ZXJpbmcgdGhyZXNob2xkcyB0aGF0IG1heSBuZWVkIHRvIGJlIGFwcGxpZWQuCgpJZiB5b3UncmUgY29taW5nIGZyb20gc2NSTkEtU2VxIHdvcmssIHRoZXNlIGxvdyBudW1iZXJzIHByb2JhYmx5IGxvb2sgdmVyeSBhbGFybWluZy4gSG93IGNhbiB5b3UgcG9zc2libHkgd29yayB3aXRoIDMxIG1lZGlhbiBnZW5lcyBwZXIgY2VsbD8KClVubGlrZSBzY1JOQS1TZXEgZGF0YSBhbmQgc2VxdWVuY2luZy1iYXNlZCBTVCwgYm90aCBnZW5lIGRyb3BvdXRzIGFuZCBub2lzZSBhcmUgbXVjaCwgbXVjaCBsb3dlciBpbiAqaW4gc2l0dSogU1QgZGF0YS4gCgpXZSBhcmUgYWxzbyB3b3JraW5nIHdpdGggMTAwLWZvbGQgZmV3ZXIgdGFyZ2V0dGVkIGdlbmVzLgoKCmBgYHtyfQpnZ3Bsb3Qoc2V1cmF0X0NSQzJbW11dLCBhZXMobkZlYXR1cmVfWEVOSVVNKSkgKyBnZW9tX2RlbnNpdHkoKQpxdWFudGlsZShzZXVyYXRfQ1JDMiRuRmVhdHVyZV9YRU5JVU0sIGMoMC4wMSwgMC4xLCAwLjUsIDAuOSwgMC45OSkpCmBgYApVc2luZyAqSW1hZ2VGZWF0dXJlUGxvdCogdG8gdmlzdWFsaXplIHRoZSBjZWxsIGFyZWEgaW4gc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YSBhbGxvd3MgdXMgdG8gZXhhbWluZSB0aGUgc3BhdGlhbCBvcmdhbml6YXRpb24gYW5kIHBvdGVudGlhbCBoZXRlcm9nZW5laXR5IG9mIGNlbGwgc2l6ZXMgd2l0aGluIHlvdXIgdGlzc3VlIHNhbXBsZS4KCioqV2h5IGRvIHdlIGdldCBzdWNoIGEgZGlmZmVyZW5jZSBpbiBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiBjZWxsIHNpemVzPyoqCgpUaGlzIGNvdWxkIGJlIGR1ZSB0byBiaW9sb2dpY2FsIGRpZmZlcmVuY2VzIGJldHdlZW4gc21hbGwgYW5kIGxhcmdlIGNlbGxzIC0gZS5nLiBzbWFsbCBjZWxscyBsaWtlIFQtY2VsbHMuIAoKSG93ZXZlciwgaGVyZSB0aGUgc2lnbmFsIGNvcnJlbGF0ZXMgd2l0aCBhcmVhcyBvZiBsb3cgY2VsbHVsYXJpc2F0aW9uLiBUaGVyZWZvcmUsIGl0IGlzIGxpa2VseSB0aGlzIGlzIGFuIGFydGVmYWN0IG9mIG51Y2xlaSBleHBhbnNpb24gaW4gY2VsbCBzZWdtZW50YXRpb24uIAoKV2hhdCBpcyBOdWNsZWkgRXhwYW5zaW9uPwoKTnVjbGVpIGV4cGFuc2lvbiBpbiBjZWxsIHNlZ21lbnRhdGlvbiByZWZlcnMgdG8gdGhlIHByb2Nlc3Mgb2YgZW5sYXJnaW5nIHRoZSBzZWdtZW50ZWQgbnVjbGVpIHJlZ2lvbnMgdG8gYXBwcm94aW1hdGUgdGhlIGJvdW5kYXJpZXMgb2YgdGhlIGVudGlyZSBjZWxscy4gVGhpcyB0ZWNobmlxdWUgaXMgdXNlZCB0byBiZXR0ZXIgcmVwcmVzZW50IHRoZSBhY3R1YWwgY2VsbCBib3VuZGFyaWVzIHdoZW4gb25seSB0aGUgbnVjbGVpIGhhdmUgYmVlbiBleHBsaWNpdGx5IHNlZ21lbnRlZC93ZSBvbmx5IGhhdmUgREFQSSBhbmQgbm8gYWRkaXRpb25hbCBjZWxsIGJvdW5kYXJ5IHN0YWluaW5nLiBUaGUgcHJpbWFyeSBnb2FsIGlzIHRvIHByb3ZpZGUgYSBtb3JlIGFjY3VyYXRlIGVzdGltYXRpb24gb2YgdGhlIGNlbGx1bGFyIGFyZWEsIHdoaWNoIGlzIGNydWNpYWwgZm9yIHZhcmlvdXMgZG93bnN0cmVhbSBhbmFseXNlcyBpbiBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBhbmQgc2luZ2xlLWNlbGwgc3R1ZGllcy4gSW4gdGhpcyBjYXNlLCBudWNsZWkgZXhwYW5zaW9uIGlzIGNvbnN0cmFpbmVkIGVpdGhlciBieSBtYXhpbXVtIGRpc3RhbmNlIG9yIG90aGVyIG5lYXJieSBjZWxscyAtIHNvLCB3aGVyZSB0aGVyZSBhcmUgbm8gb3RoZXIgbmVhcmJ5IGNlbGxzIHRvICJidW1wIGludG8iLCB0aGUgZXhwYW5zaW9uIGdlbmVyYXRlcyBhcnRpZmljaWFsbHkgYmlnZ2VyIGNlbGxzLgoKCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiY2VsbF9hcmVhIiwgYXhlcyA9IFQpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKV2UgY2FuIGZ1cnRoZXIgY2hlY2sgdGhhdCB0aGlzIGlzIGxpa2VseSB0aGUgY2FzZSBieSBwbG90dGluZyB0aGUgcmF0aW8gYmV0d2VlbiBudWNsZWkgYW5kIHRvdGFsIGNlbGwgYXJlYS4gV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGlzIGEgdmVyeSBiaWcgZGVjcmVhc2UgaW4gcGVyY2VudGFnZSBvZiBjZWxsIGFyZWEgb2NjdXBpZWQgYnkgbnVjbGV1cyBpbiBhcmVhcyBvZiBsb3cgY2VsbCBkZW5zaXR5LgoKVGhlIGNlbGwtdG8tbnVjbGV1cyBhcmVhIHJhdGlvIGNhbiBhbHNvIHBvdGVudGlhbGx5IHByb3ZpZGUgaW5zaWdodHMgaW50byBjZWxsIG1vcnBob2xvZ3ksIGNlbGwgdHlwZSBhbmQgcG90ZW50aWFsIGNoYW5nZXMgaW4gY2VsbHVsYXIgc3RhdGVzIG9yIGNvbmRpdGlvbnMuIEZvciBleGFtcGxlLCBULUNlbGxzIGNhbiBvZnRlbiBiZSBxdWl0ZSB3ZWxsIGlkZW50aWZpZWQgYnkgdGhpcyB2YXJpYWJsZSBhbG9uZSwgYXMgdGhleSBoYXZlIGEgc21hbGwgY3l0b3BsYXNtIHZvbHVtZS4gIEhvd2V2ZXIsIHdpdGhvdXQgYSBjZWxsIGJvdW5kYXJ5IHN0YWluLCB0aGlzIG1ldHJpYyBtYWlubHkgY2FwdHVyZXMgc2VnbWVudGF0aW9uIGFydGVmYWN0cywgc28gYmUgY2FyZWZ1bCBhYm91dCBvdmVyLWludGVycHJldGF0aW9uIQoKYGBge3J9CnNldXJhdF9DUkMyJGNlbGxfbnVjbGV1c19yYXRpbyA8LSBzZXVyYXRfQ1JDMiRudWNsZXVzX2FyZWEgLyBzZXVyYXRfQ1JDMiRjZWxsX2FyZWEKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgImNlbGxfbnVjbGV1c19yYXRpbyIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCklmIHdlIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiwgd2Ugc2VlIHRoYXQgd2UgaGF2ZSBhIGJpZyB0YWlsIGVuZCBvZiBvdmVybHkgbGFyZ2UgY2VsbHMuCgpgYGB7cn0KZ2dwbG90KHNldXJhdF9DUkMyW1tdXSwgYWVzKGNlbGxfYXJlYSkpICsgZ2VvbV9kZW5zaXR5KCkKYGBgCkluIHRoaXMgY2FzZSwgd2UgY2FuIHNlZSB0aGF0IGFzIGV4cGVjdGVkLCB0aGVyZSBpcyBnZW5lcmFsbHkgYSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGNlbGwgYXJlYSBhbmQgdHJhbnNjcmlwdCBkZXRlY3Rpb24gcmF0ZS4gCgpIb3dldmVyLCB3ZSBhbHNvIGhhdmUgYSBncm91cCBvZiBjZWxscyB3aGVyZSB0aGlzIGlzIG5vdCB0aGUgY2FzZSAtIHZlcnkgbGFyZ2UgY2VsbHMgYnV0IHJlbGF0aXZlbHkgZmV3IHRyYW5zY3JpcHRzLiBUaGVzZSBjZWxscyBhcmUgbWFpbmx5IHN1Ym11Y29zYWwgc3Ryb21hbCBjZWxscyB3aGljaCBhcmUgdmVyeSBwb29ybHkgY292ZXJlZCBieSB0aGUgcGFuZWwgMTB4IGhhdmUgdXNlZC4gCgoKYGBge3J9CmdncGxvdChzZXVyYXRfQ1JDMltbXV0sIGFlcyhuQ291bnRfWEVOSVVNLCBjZWxsX2FyZWEpKSArIGdlb21fcG9pbnQoKSAKYGBgCldlIGNhbiBjcmVhdGUgYSBmaWx0ZXIgdG8gcmVtb3ZlIHRoZSBvdmVybHkgbGFyZ2UgY2VsbHMgZnJvbSB0aGUgYW5hbHlzaXMuCgoqcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC45OSkqOiBDYWxjdWxhdGVzIHRoZSA5OXRoIHBlcmNlbnRpbGUgb2YgdGhlIGNlbGxfYXJlYSB2YWx1ZXMgaW4gdGhlIFNldXJhdCBvYmplY3QuIFRoaXMgdmFsdWUgc2VydmVzIGFzIGEgdGhyZXNob2xkIHRvIGlkZW50aWZ5IHRoZSBsYXJnZXN0IDElIG9mIGNlbGxzIC0gYnV0IHdoYXQgaXMgYSBzZW5zaWJsZSB0aHJlc2hvbGQsIGlmIGFueSwgZGVwZW5kcyBvbiB5b3VyIHRpc3N1ZS4KCipzZXVyYXQkY2VsbF9hcmVhIDwgcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC45OSkqOiBDb21wYXJlcyBlYWNoIGNlbGwncyBhcmVhIHRvIHRoZSA5OXRoIHBlcmNlbnRpbGUgdGhyZXNob2xkLiBUaGUgcmVzdWx0IGlzIGEgbG9naWNhbCB2ZWN0b3Igd2hlcmUgZWFjaCBlbGVtZW50IGlzIFRSVUUgaWYgdGhlIGNvcnJlc3BvbmRpbmcgY2VsbCdzIGFyZWEgaXMgbGVzcyB0aGFuIHRoZSA5OXRoIHBlcmNlbnRpbGUgYW5kIEZBTFNFIG90aGVyd2lzZS4KCgoqc2V1cmF0W1siU0laRV9GSUxURVJfTEFSR0UiXV0qOiBDcmVhdGVzIGEgbmV3IG1ldGFkYXRhIGZpZWxkIG5hbWVkIFNJWkVfRklMVEVSX0xBUkdFIGluIHRoZSBTZXVyYXQgb2JqZWN0LCBzdG9yaW5nIHRoZSBsb2dpY2FsIHZlY3Rvci4KCgpgYGB7cn0Kc2V1cmF0X0NSQzJbWyJTSVpFX0ZJTFRFUl9MQVJHRSJdXSA8LSBzZXVyYXRfQ1JDMiRjZWxsX2FyZWEgPCBxdWFudGlsZShzZXVyYXRfQ1JDMiRjZWxsX2FyZWEsIC45OSkKYGBgCgpOb3cgd2UgY2FuIHVzZSAqSW1hZ2VEaW1QbG90KiB0byB2aXN1YWxpc2UgdGhlIGNlbGxzIHdoaWNoIGhhdmUgYmVlbiBmbGFnZ2VkIGZvciByZW1vdmFsLgoKV2UgY2FuIHNlZSB0aGF0IHRoZXNlIGFyZSBtb3N0bHkgaW4gdGhlIHN1Ym11Y29zYSByZWdpb24uIAoKKipIb3cgZG8gZGlmZmVyZW50IHRocmVzaG9sZHMgYmVoYXZlPyBJcyB0aGVyZSBhIG1vcmUgYXBwcm9wcmlhdGUgb25lIHRvIHVzZT8gSXMgYW55IG5lY2Vzc2FyeSBhdCBhbGw/KioKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5PSJTSVpFX0ZJTFRFUl9MQVJHRSIpCmBgYApXZSBjYW4gdXNlIHRoZSBzYW1lIGFwcHJvYWNoIHRvIGNyZWF0ZSBhIGZpbHRlciBmb3Igc2VnbWVudGVkIGNlbGxzIHdoaWNoIGFyZSB2ZXJ5IHNtYWxsIGFuZCBsaWtlbHkgc2VnbWVudGF0aW9uIGFyZmV0YWN0cy4gCgoqcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC4wMSkqOiBDYWxjdWxhdGVzIHRoZSAxc3QgcGVyY2VudGlsZSBvZiB0aGUgY2VsbF9hcmVhIHZhbHVlcyBpbiB0aGUgU2V1cmF0IG9iamVjdC4gVGhpcyB2YWx1ZSBzZXJ2ZXMgYXMgYSB0aHJlc2hvbGQgdG8gaWRlbnRpZnkgdGhlIHNtYWxsZXN0IDElIG9mIGNlbGxzLgoKKnNldXJhdCRjZWxsX2FyZWEgPiBxdWFudGlsZShzZXVyYXQkY2VsbF9hcmVhLCAwLjAxKSo6IENvbXBhcmVzIGVhY2ggY2VsbCdzIGFyZWEgdG8gdGhlIDFzdCBwZXJjZW50aWxlIHRocmVzaG9sZC4gVGhlIHJlc3VsdCBpcyBhIGxvZ2ljYWwgdmVjdG9yIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBUUlVFIGlmIHRoZSBjb3JyZXNwb25kaW5nIGNlbGwncyBhcmVhIGlzIGdyZWF0ZXIgdGhhbiB0aGUgMXN0IHBlcmNlbnRpbGUgYW5kIEZBTFNFIG90aGVyd2lzZS4KCipzZXVyYXRbWyJTSVpFX0ZJTFRFUl9TTUFMTCJdXSo6IENyZWF0ZXMgYSBuZXcgbWV0YWRhdGEgZmllbGQgbmFtZWQgU0laRV9GSUxURVJfU01BTEwgaW4gdGhlIFNldXJhdCBvYmplY3QsIHN0b3JpbmcgdGhlIGxvZ2ljYWwgdmVjdG9yLgoKCmBgYHtyfQpzZXVyYXRfQ1JDMltbIlNJWkVfRklMVEVSX1NNQUxMIl1dIDwtIHNldXJhdF9DUkMyJGNlbGxfYXJlYSA+IHF1YW50aWxlKHNldXJhdF9DUkMyJGNlbGxfYXJlYSwgLjAxKQpgYGAKCk5vdyB3ZSBjYW4gdXNlICpJbWFnZURpbVBsb3QqIHRvIHZpc3VhbGlzZSB0aGUgY2VsbHMgd2hpY2ggaGF2ZSBiZWVuIGZsYWdnZWQgZm9yIHJlbW92YWwuCgpXZSBjYW4gc2VlIHRoYXQgdGhlc2UgYXJlIG1vcmUgc2NhdHRlcmVkIHRocm91Z2hvdXQgdGhlIHRpc3N1ZSAtIGJ1dCB0aGVyZSBtYXkgYmUgbW9yZSBpbiB0aGUgZm9sbGljdWxhciByZWdpb25zLiAKCioqSG93IGRvIGRpZmZlcmVudCB0aHJlc2hvbGRzIGJlaGF2ZT8gSXMgdGhlcmUgYSBtb3JlIGFwcHJvcHJpYXRlIG9uZSB0byB1c2U/IElzIGFueSBuZWNlc3NhcnkgYXQgYWxsPyoqCgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBncm91cC5ieT0iU0laRV9GSUxURVJfU01BTEwiKQpgYGAKV2UgY2FuIGNoZWNrIGhvdyB0aGVzZSB2YWx1ZXMgY29ycmVsYXRlIHdpdGggZ2VuZSBkZXRlY3Rpb24gcmF0ZS4gCgpJZiB3ZSBmaWx0ZXIgb3V0IHNtYWxsIGNlbGxzLCB3ZSB3aWxsIHJlbW92ZSBjZWxscyB3aXRoIGxvdyBudW1iZXJzIG9mIGdlbmVzIGRldGVjdGVkLiAKCklmIHdlIGZpbHRlciBvdXQgbGFyZ2UgY2VsbHMsIHRoaXMgaXMgbm90IHRoYXQgYmlhc2VkIHRvd2FyZHMgb3Zlcmx5IGxhcmdlIGNvdW50cywgYXMgd2Ugc2F3IGJlZm9yZS4KCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9N30KcDEgPC0gVmxuUGxvdChzZXVyYXRfQ1JDMiwgIm5GZWF0dXJlX1hFTklVTSIsIGdyb3VwLmJ5ID0gIlNJWkVfRklMVEVSX1NNQUxMIiwgcHQuc2l6ZSA9IC4xLCBhbHBoYSA9IC41KSArIGxhYnModGl0bGU9IlNtYWxsIENlbGwgRmlsdGVyIikKcDIgPC0gVmxuUGxvdChzZXVyYXRfQ1JDMiwgIm5GZWF0dXJlX1hFTklVTSIsIGdyb3VwLmJ5ID0gIlNJWkVfRklMVEVSX0xBUkdFIiwgcHQuc2l6ZSA9IC4xLCBhbHBoYSA9IC41KSsgbGFicyh0aXRsZT0iTGFyZ2UgQ2VsbCBGaWx0ZXIiKQoKcDEgKyBwMgpgYGAKQWRqdXN0aW5nIHRoZSB0aHJlc2hvbGQgZm9yIHdoYXQgaXMgY29uc2lkZXJlZCBhICJzbWFsbCBjZWxsIiBjYW4gaGF2ZSBzaWduaWZpY2FudCBpbXBsaWNhdGlvbnMgZm9yIHlvdXIgYW5hbHlzaXMsIGVzcGVjaWFsbHkgaW4gYXJlYXMgd2l0aCBzcGVjaWZpYyBjZWxsIHR5cGVzIHN1Y2ggYXMgVC1jZWxscywgd2hpY2ggYXJlIHNtYWxsIGFuZCBkZW5zZWx5IHBhY2tlZCBpbiBmb2xsaWN1bGFyIHJlZ2lvbnMuIFRoaXMgZXhhbXBsZSBkZW1vbnN0cmF0ZXMgaG93IGNoYW5naW5nIHRoZSB0aHJlc2hvbGQgdG8gdGhlIDEwdGggcGVyY2VudGlsZSBhZmZlY3RzIHRoZSBmaWx0ZXJpbmcuIEluIHRoaXMgY2FzZSwgd2Ugd291bGQgcHJvYmFibHkgZmlsdGVyIG91dCBhIGxvdCBvZiBnb29kIGNlbGxzIHRoYXQgd2UgZG9uJ3Qgd2FudCB0byBsb3NlISBTbywgYmUgY2FyZWZ1bCB3aGVuIGxvb2tpbmcgYXQgdGhlc2UgdHlwZXMgb2YgUUMgbWV0cmljcyEKCgpgYGB7cn0Kc2V1cmF0X0NSQzJbWyJTSVpFX0ZJTFRFUl9TTUFMTCJdXSA8LSBzZXVyYXRfQ1JDMiRjZWxsX2FyZWEgPiBxdWFudGlsZShzZXVyYXRfQ1JDMiRjZWxsX2FyZWEsIC4xKQpgYGAKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5PSJTSVpFX0ZJTFRFUl9TTUFMTCIpCmBgYApMZXRzIHNldCB0aGlzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIDElIHRocmVzaG9sZC4KYGBge3J9CnNldXJhdF9DUkMyW1siU0laRV9GSUxURVJfU01BTEwiXV0gPC0gc2V1cmF0X0NSQzIkY2VsbF9hcmVhID4gcXVhbnRpbGUoc2V1cmF0X0NSQzIkY2VsbF9hcmVhLCAuMDEpCmBgYAoKClRoZSBtb3N0IGltcG9ydGFudCBmaWx0ZXIgaXMgdGhlIG92ZXJhbGwgdHJhbnNjcmlwdCBkZXRlY3Rpb24uIEVtcHR5IGNlbGxzIG9yIGNlbGxzIHdpdGggdmVyeSBsb3cgdHJhbnNjcmlwdCBjb3VudCBjYW5ub3QgYmUgdGFrZW4gZm9yd2FyZCBmb3IgY2x1c3RlcmluZyBhbmFseXNpcyBhbmQgaXQgaXMgZXh0cmVtZWx5IGRpZmZpY3VsdCB0byBpZGVudGlmeSB3aGF0IHRoZXkgbWF5IGJlLiBIZXJlLCB3ZSBzZXQgYSB0aHJlc2hvbGQgb2YgbWluaW11bSAxNSB0cmFuc2NyaXB0cy4gVGhpcyBzZWVtcyBxdWl0ZSBsb3cgLSBmb3IgZGF0YSBmcm9tICppbiBzaXR1KiBwbGF0Zm9ybXMgd2l0aCBsb3cgbm9pc2UgKFhlbml1bSwgTWVyZmlzaCwgTWVyc2NvcGUpLCB0aGlzIGlzIGdlbmVyYWxseSBlbm91Z2ggdG8gY2x1c3RlciBhbmQgaWRlbnRpZnkgY2VsbCB0eXBlcy4gSWYgeW91ciBkYXRhIGhhcyBtb3JlIG5vaXNlIChlLmcuIENvc014KSwgYSBoaWdoZXIgdGhyZXNob2xkIGlzIG1vcmUgYXBwcm9wcmlhdGUuCgoKKnNldXJhdCRuQ291bnRfWEVOSVVNID49IDE1KjogQ29tcGFyZXMgZWFjaCBjZWxsJ3MgdHJhbnNjcmlwdCBjb3VudCB0byB0aGUgdGhyZXNob2xkIG9mIDE1LiBUaGUgcmVzdWx0IGlzIGEgbG9naWNhbCB2ZWN0b3Igd2hlcmUgZWFjaCBlbGVtZW50IGlzIFRSVUUgaWYgdGhlIGNvcnJlc3BvbmRpbmcgY2VsbCBoYXMgYXQgbGVhc3QgMTUgdHJhbnNjcmlwdHMgYW5kIEZBTFNFIG90aGVyd2lzZS4KKnNldXJhdCRUUkFOU0NSSVBUX0ZJTFRFUio6IENyZWF0ZXMgYSBuZXcgbWV0YWRhdGEgZmllbGQgbmFtZWQgVFJBTlNDUklQVF9GSUxURVIgaW4gdGhlIFNldXJhdCBvYmplY3QsIHN0b3JpbmcgdGhlIGxvZ2ljYWwgdmVjdG9yLgoKCmBgYHtyfQpzZXVyYXRfQ1JDMiRUUkFOU0NSSVBUX0ZJTFRFUiA8LSBzZXVyYXRfQ1JDMiRuQ291bnRfWEVOSVVNID49IDE1CmBgYAoKQW5kIHdlIGNhbiB2aXN1YWxpc2UgdGhlIGNlbGxzIHRoYXQgd2Ugd291bGQgbG9zZS4gCgpXZSBzZWUgdGhhdCB3ZSBkaXNwcm9wb3J0aW9uYXRlbHkgd291bGQgZmlsdGVyIG91dCBtb3JlIGNlbGxzIGZyb20gc29tZSByZWdpb25zIHRoYW4gb3RoZXJzLiBBcyBwb2ludGVkIG91dCBwcmV2aW91c2x5LCB0aGlzIGlzIGxpa2VseSBkdWUgdG8gYSBjb21iaW5hdGlvbiBvZiBnZW5lIHBhbmVsIGNvdmVyYWdlIGluIHNvbWUgcmVnaW9ucyBhbmQgdmVyeSBzbWFsbCBjZWxscyBpbiBkZW5zZWx5IHBhY2tlZCByZWdpb25zIGxpa2UgZm9sbGljbGVzLgoKYGBge3J9CkltYWdlRGltUGxvdChzZXVyYXRfQ1JDMiwgZ3JvdXAuYnk9IlRSQU5TQ1JJUFRfRklMVEVSIikKYGBgCkZpbmFsbHksIHZpc3VhbGl6aW5nIHRoZSBjb3VudHMgb2YgbmVnYXRpdmUgY29udHJvbCBjb2Rld29yZHMsIG5lZ2F0aXZlIGNvbnRyb2wgcHJvYmVzLCBhbmQgdW5hc3NpZ25lZCBjb2Rld29yZHMgaGVscHMgaWRlbnRpZnkgYW5kIHVuZGVyc3RhbmQgdGVjaG5pY2FsIGFydGlmYWN0cyBhbmQgYmFja2dyb3VuZCBub2lzZSBpbiB5b3VyIHNwYXRpYWwgdHJhbnNjcmlwdG9taWNzIGRhdGEuCgpIZXJlLCB3ZSBjYW4gc2VlIHRoYXQgYWxsIGNvbnRyb2wgcHJvYmVzIGFuZCBjb2Rld29yZHMgcHJvZHVjZSB5aWVsZCB2ZXJ5IGxpdHRsZSBzaWduYWwsIHN1Z2dlc3Rpbmcgb3VyIGRhdGEgaXMgZ29vZCBxdWFsaXR5ISAKCkluIHNvbWUgY2FzZXMsIGhpZ2ggYW1vdW50IG9mIGF1dG9mbG91cmVzY2VuY2UgaXMgdGhlIGNlbGxzL3Rpc3N1ZSBjYW4gc29tZXRpbWVzIGdlbmVyYXRlIGZhbHNlIHBvc2l0aXZlIHNpZ25hbCBhbmQgdGhpcyBzaG91bGQgYmUgZmlsdGVyZWQgb3V0LiAKCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTd9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJuQ291bnRfTmVnYXRpdmUuQ29udHJvbC5Db2Rld29yZCIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAibkNvdW50X05lZ2F0aXZlLkNvbnRyb2wuUHJvYmUiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIm5Db3VudF9VbmFzc2lnbmVkLkNvZGV3b3JkIikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYAoKQWx0aG91Z2ggdGhlIG5lZ2F0aXZlIGNvbnRyb2wgc2lnbmFsIGlzIGxvdywgd2UgY2FuIG5vbmV0aGVsZXNzIGNyZWF0ZSBhIGZpbHRlciB0byByZW1vdmUgY2VsbHMgd2hpY2ggaGF2ZSBhbnksIGFsdGhvdWdoIGluIHRoaXMgY2FzZSBpdCBpcyBwcm9iYWJseSB1bm5lY2Vzc2FyeS4KCmBgYHtyfQpzZXVyYXRfQ1JDMiRQUk9CRV9GSUxURVIgPC0gc2V1cmF0X0NSQzIkbkNvdW50X1VuYXNzaWduZWQuQ29kZXdvcmQgPT0gMCAmCiAgICAgICAgICAgICAgICAgICAgICAgc2V1cmF0X0NSQzIkbkNvdW50X05lZ2F0aXZlLkNvbnRyb2wuQ29kZXdvcmQgPT0gMCAmCiAgICAgICAgICAgICAgICAgICAgICAgc2V1cmF0X0NSQzIkbkNvdW50X05lZ2F0aXZlLkNvbnRyb2wuUHJvYmUgPT0gMApgYGAKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5PSJQUk9CRV9GSUxURVIiKQpgYGAKRmluYWxseSwgd2UgY2FuIHN1YnNldCB0aGUgc2V1cmF0IG9iamVjdCBiYXNlZCBvbiBhbnkvYWxsIG9mIHRoZSBmaWx0ZXJzIHdlIGhhdmUgY3JlYXRlZCBlYXJsaWVyLiAKCkJ5IGNvbWJpbmluZyBwcm9iZSwgc2l6ZSwgYW5kIHRyYW5zY3JpcHQgZmlsdGVycywgeW91IGNhbiByZXRhaW4gb25seSB0aGUgY2VsbHMgdGhhdCBtZWV0IGFsbCBxdWFsaXR5IGNyaXRlcmlhLCByZWR1Y2luZyB0aGUgaW1wYWN0IG9mIHRlY2huaWNhbCBhcnRpZmFjdHMgYW5kIG5vaXNlIG9uIHlvdXIgYW5hbHlzaXMuCgpgYGB7cn0Kc2V1cmF0X0NSQzIgPC0gc3Vic2V0KHNldXJhdF9DUkMyLCBQUk9CRV9GSUxURVIgJiBTSVpFX0ZJTFRFUl9MQVJHRSAmIFNJWkVfRklMVEVSX1NNQUxMICYgVFJBTlNDUklQVF9GSUxURVIpCmBgYApMZXRzIGV4YW1pbmUgdGhlIGNsZWFuZWQgdXAgb2JqZWN0IC0gd2UgaGF2ZSBsb3N0IGEgZmV3IHRob3VzYW5kIGNlbGxzIGZyb20gdGhlIGFuYWx5c2lzLiAKYGBge3J9CnNldXJhdApJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIpCnNhdmVSRFMoc2V1cmF0X0NSQzIsIGZpbGU9IkNSQzJfc3Vic2V0X2ZpbHRlcmVkLlJEUyIpCmBgYAoqKkRhdGEgTm9ybWFsaXNhdGlvbioqCgpUaGUgKlNDVHJhbnNmb3JtKiBmdW5jdGlvbiBpbiBTZXVyYXQgaXMgdXNlZCBmb3Igbm9ybWFsaXppbmcgc2luZ2xlLWNlbGwgUk5BLXNlcSBhbmQgc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YS4gVGhpcyBtZXRob2QgbW9kZWxzIHRoZSBnZW5lIGV4cHJlc3Npb24gY291bnRzIHVzaW5nIGEgcmVndWxhcml6ZWQgbmVnYXRpdmUgYmlub21pYWwgcmVncmVzc2lvbiBhbmQgcmVtb3ZlcyB0ZWNobmljYWwgbm9pc2Ugd2hpbGUgcHJlc2VydmluZyBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5LiBUaGUgKmNsaXAucmFuZ2UqIHBhcmFtZXRlciBpcyB1c2VkIHRvIGxpbWl0IHRoZSByYW5nZSBvZiB0aGUgdHJhbnNmb3JtZWQgdmFsdWVzLCB3aGljaCBjYW4gaGVscCBzdGFiaWxpemUgZG93bnN0cmVhbSBhbmFseXNlcyBieSBsaW1pdGluZyB0aGUgaW5mbHVlbmNlIG9mIGV4dHJlbWUgdmFsdWVzLiAKCgpgYGB7cn0Kc2V1cmF0X0NSQzIgPC0gU0NUcmFuc2Zvcm0oc2V1cmF0X0NSQzIsIGFzc2F5ID0gIlhFTklVTSIsIGNsaXAucmFuZ2UgPSBjKC0xMCwgMTApKQpgYGAKClByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgaXMgYSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlIHVzZWQgdG8gaWRlbnRpZnkgdGhlIHByaW1hcnkgYXhlcyBvZiB2YXJpYXRpb24gaW4gaGlnaC1kaW1lbnNpb25hbCBkYXRhLiBJbiB0aGUgY29udGV4dCBvZiBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcywgUENBIGhlbHBzIHRvIHJlZHVjZSB0aGUgY29tcGxleGl0eSBvZiB0aGUgZGF0YSB3aGlsZSBwcmVzZXJ2aW5nIHRoZSBtb3N0IGltcG9ydGFudCBwYXR0ZXJucyBvZiB2YXJpYXRpb24uIAoKClRJUDogSWYgeW91ciB0YXJnZXQgcGFuZWwgaXMgdmVyeSBzbWFsbCwgeW91IGNhbiBza2lwIHRoaXMgc3RlcCBhbmQgY2Fycnkgb3V0IGNsdXN0ZXJpbmcgYW5hbHlzaXMgZGlyZWN0bHkgb24gZ2VuZSBleHByZXNzaW9uLiBUaGlzIGNhbiBzb21ldGltZXMgaGVscCB3aXRoIGFjaGlldmluZyBiZXR0ZXIgY2x1c3RlcmluZyByZXN1bHRzLgpgYGB7cn0Kc2V1cmF0X0NSQzIgPC0gUnVuUENBKHNldXJhdF9DUkMyKQpgYGAKQXMgYmVmb3JlLCB3ZSBjYW4gdmlzdWFsaXNlIGhvdyBtdWNoIHZhcmlhdGlvbiBpcyBjYXB0dXJlZCBieSBlYWNoIFBDLiAKClRoZSBFbGJvd1Bsb3QgZnVuY3Rpb24gaGVscHMgdG8gZGV0ZXJtaW5lIHRoZSBudW1iZXIgb2Ygc2lnbmlmaWNhbnQgUENzIHRvIHVzZSBmb3IgZG93bnN0cmVhbSBhbmFseXNlcy4gVGhlIHBsb3QgdHlwaWNhbGx5IHNob3dzIHRoZSBhbW91bnQgb2YgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGVhY2ggUEMsIGFuZCB0aGUgImVsYm93IiBwb2ludCBpbmRpY2F0ZXMgYSBuYXR1cmFsIGN1dG9mZi4KCgpgYGB7cn0KRWxib3dQbG90KHNldXJhdF9DUkMyLCA1MCkKYGBgClBsb3R0aW5nIHRoZSB0b3AgZ2VuZXMgY29udHJpYnV0aW5nIHRvIGEgc3BlY2lmaWMgcHJpbmNpcGFsIGNvbXBvbmVudCBoZWxwcyBpbiB1bmRlcnN0YW5kaW5nIHRoZSBiaW9sb2dpY2FsIGZhY3RvcnMgZHJpdmluZyB0aGUgdmFyaWF0aW9uIGNhcHR1cmVkIGJ5IHRoYXQgY29tcG9uZW50LiBUaGlzIHR5cGUgb2YgcGxvdCBoaWdobGlnaHRzIHRoZSBnZW5lcyB3aXRoIHRoZSBoaWdoZXN0IGxvYWRpbmdzLCB3aGljaCBhcmUgdGhlIG1vc3QgaW5mbHVlbnRpYWwgaW4gdGhlIHByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMuCgpgYGB7ciBmaWcuaGVpZ2h0PTksIGZpZy53aWR0aD03fQpQQ19QbG90dGluZyhzZXVyYXRfQ1JDMiwgZGltX251bWJlciA9IDEpCmBgYAoKVGhlICpGZWF0dXJlUGxvdCogZnVuY3Rpb24gaW4gU2V1cmF0IGlzIHVzZWQgdG8gdmlzdWFsaXplIHRoZSBleHByZXNzaW9uIG9mIGEgc3BlY2lmaWMgZ2VuZSBhY3Jvc3MgY2VsbHMgaW4gYSBnaXZlbiBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gc3BhY2UgKGUuZy4sIFBDQSkuIFRoaXMgaGVscHMgdG8gdW5kZXJzdGFuZCBob3cgdGhlIGV4cHJlc3Npb24gb2YgYSBnZW5lIHZhcmllcyBhY3Jvc3MgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzLgoKYGBge3J9CkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiQ0VBQ0FNNSIsIHJlZHVjdGlvbiA9ICJwY2EiKSArIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpCmBgYApXZSBjYW4gYWxzbyBleGFtaW5lIGhvdyB2YXJpb3VzIFBDcyBhcmUgZGlzdHJpYnV0ZWQgc3BhdGlhbGx5LiAKCkhlcmUsIHdlIGNhbiBzZWUgdGhhdCBoaWdoIFBDMSBsb2FkaW5ncyBlbnJpY2ggaW4gZm9sbGljdWxhciBzdHJ1Y3R1cmVzIGFuZCBsb3cgUEMxIGxvYWRpbmdzIGVucmljaCBpbiBjcnlwdCB0b3AgY2VsbHMuCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiUENfMSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCldlIGNhbiBwbG90IHRoZSBleHByZXNzaW9uIG9mIGhpZ2ggKG9yIGxvdykgbG9hZGluZyBnZW5lcyB0byB2aXN1YWxpc2UgaG93IHRoaXMgY29ycmVsYXRlcyB3aXRoIG91ciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24uCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiSUdGQlA3Iiwgc2l6ZT0uNSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYApOZXh0LCB3ZSB3aWxsIHVzZSB0aGUgcmVkdWNlZCBkaW1lbnNpb25hbGl0eSBkYXRhIGZvciBjbHVzdGVyaW5nIGFuZCBjbHVzdGVyIHZpc3VhbGlzYXRpb24uIAoKKlJ1blVNQVAqOiBQZXJmb3JtIFVuaWZvcm0gTWFuaWZvbGQgQXBwcm94aW1hdGlvbiBhbmQgUHJvamVjdGlvbiAoVU1BUCkgdG8gcmVkdWNlIHRoZSBkaW1lbnNpb25hbGl0eSBvZiB0aGUgZGF0YSBmb3IgdmlzdWFsaXphdGlvbi4gVGhlIFVNQVAgcGxvdCByZWR1Y2VzIHRoZSBoaWdoLWRpbWVuc2lvbmFsIGRhdGEgdG8gdHdvIGRpbWVuc2lvbnMsIHByZXNlcnZpbmcgdGhlIGxvY2FsIGFuZCBnbG9iYWwgc3RydWN0dXJlIG9mIHRoZSBkYXRhIGZvciB2aXN1YWxpemF0aW9uLiBDZWxscyB0aGF0IGFyZSBjbG9zZSB0b2dldGhlciBpbiB0aGUgVU1BUCBwbG90IGFyZSBzaW1pbGFyIGluIHRoZWlyIGdlbmUgZXhwcmVzc2lvbiBwcm9maWxlcy4KKnNldXJhdCo6IFRoZSBTZXVyYXQgb2JqZWN0LgoqZGltcyA9IDE6MjAqOiBTcGVjaWZpZXMgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzIHRvIHVzZSBmb3IgVU1BUC4KCipGaW5kTmVpZ2hib3JzKjogRmluZGluZyBuZWFyZXN0IG5laWdoYm9ycyBoZWxwcyB0byBpZGVudGlmeSBjZWxscyB0aGF0IGFyZSBzaW1pbGFyIGJhc2VkIG9uIHRoZWlyIFBDQSBzY29yZXMsIHdoaWNoIGlzIHVzZWQgZm9yIGNsdXN0ZXJpbmcuCipzZXVyYXQqOiBUaGUgU2V1cmF0IG9iamVjdC4KKnJlZHVjdGlvbiA9ICJwY2EiKjogU3BlY2lmaWVzIHRoYXQgdGhlIFBDQSBzcGFjZSBzaG91bGQgYmUgdXNlZCBmb3IgZmluZGluZyBuZWlnaGJvcnMuCipkaW1zID0gMToyMCo6IFNwZWNpZmllcyB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMgdG8gdXNlIGZvciBpZGVudGlmeWluZyBuZWlnaGJvcnMuCgoqRmluZENsdXN0ZXJzKjogQ2x1c3RlcmluZyBpZGVudGlmaWVzIGRpc3RpbmN0IGdyb3VwcyBvZiBjZWxscyB3aXRoIHNpbWlsYXIgZ2VuZSBleHByZXNzaW9uIHBhdHRlcm5zLiBUaGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgY29udHJvbHMgdGhlIGdyYW51bGFyaXR5IG9mIHRoZSBjbHVzdGVyaW5nLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QuCipyZXNvbHV0aW9uID0gMC43KjogU2V0cyB0aGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgZm9yIGNsdXN0ZXJpbmcuIEhpZ2hlciB2YWx1ZXMgbGVhZCB0byBtb3JlIGNsdXN0ZXJzLCB3aGlsZSBsb3dlciB2YWx1ZXMgbGVhZCB0byBmZXdlciBjbHVzdGVycy4KCmBgYHtyfQpzZXVyYXRfQ1JDMiA8LSBSdW5VTUFQKHNldXJhdF9DUkMyLCBkaW1zID0gMToyMCkKc2V1cmF0X0NSQzIgPC0gRmluZE5laWdoYm9ycyhzZXVyYXRfQ1JDMiwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjIwKQpzZXVyYXRfQ1JDMiA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0X0NSQzIsIHJlc29sdXRpb24gPSAwLjIpCnNldXJhdF9DUkMyIDwtIEZpbmRDbHVzdGVycyhzZXVyYXRfQ1JDMiwgcmVzb2x1dGlvbiA9IDAuNCkKc2V1cmF0X0NSQzIgPC0gRmluZENsdXN0ZXJzKHNldXJhdF9DUkMyLCByZXNvbHV0aW9uID0gMC42KQpzZXVyYXRfQ1JDMiA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0X0NSQzIsIHJlc29sdXRpb24gPSAwLjgpCnNldXJhdF9DUkMyIDwtIEZpbmRDbHVzdGVycyhzZXVyYXRfQ1JDMiwgcmVzb2x1dGlvbiA9IDEuMCkKc2V1cmF0X0NSQzIgPC0gRmluZENsdXN0ZXJzKHNldXJhdF9DUkMyLCByZXNvbHV0aW9uID0gMC4zKQpjbHVzdHJlZShzZXVyYXRfQ1JDMikKbGlicmFyeShjbHVzdHJlZSkKYGBgCgpOZXh0IGxldHMgdmlzdWFsaXNlIHRoZSBjbHVzdGVycyAtIGZpcnN0bHksIGJhc2VkIG9uIHRyYW5zY3JpcHRvbWUgZW1iZWRkaW5nLgoKKkRpbVBsb3QqOiBDcmVhdGVzIGEgc2NhdHRlciBwbG90IG9mIGNlbGxzIGluIGEgcmVkdWNlZC1kaW1lbnNpb25hbCBzcGFjZSwgYnkgZGVmYXVsdCBub3cgdXNpbmcgVU1BUCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24uCipzZXVyYXQqOiBUaGUgU2V1cmF0IG9iamVjdCBjb250YWluaW5nIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gcmVzdWx0cyBhbmQgY2x1c3RlciBhc3NpZ25tZW50cy4KKmxhYmVsID0gVFJVRSo6IEFkZHMgY2x1c3RlciBsYWJlbHMgdG8gdGhlIHBsb3QuCipyZXBlbCA9IFRSVUUqOiBSZXBlbHMgdGhlIGxhYmVscyB0byBhdm9pZCBvdmVybGFwcGluZywgbWFraW5nIHRoZSBwbG90IGNsZWFyZXIuCgoKYGBge3J9CkRpbVBsb3Qoc2V1cmF0X0NSQzIsIGxhYmVsPVQsIHJlcGVsPVQpCnNldXJhdF9DUkMyJFNDVF9zbm5fcmVzLjAuMgpgYGAKQW5kIG5vdyBsZXRzIHBsb3QgdGhlIGNsdXN0ZXJzIGluIHRpc3N1ZSBzcGFjZS4gCgpXZSBjYW4gc2VlIHRoYXQgb3VyIGNsdXN0ZXJzIGhhdmUgcXVpdGUgbmljZSBjb3JyZXNwb25kZW5jZSB0byBkaXN0aW5jdCBzcGF0aWFsIHJlZ2lvbnMuCmBgYHtyfQoKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBzaXplPS41KQpgYGAKQXMgYmVmb3JlLCBub3cgd2UgY2FuIHVzZSBTZXVyYXQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gZnVuY3Rpb25zIHRvIGlkZW50aWZ5IG1hcmtlciBnZW5lcyBmb3Igc3BlY2lmaWMgY2VsbCBjbHVzdGVycy4KCipGaW5kTWFya2Vycyo6IElkZW50aWZpZXMgZ2VuZXMgdGhhdCBhcmUgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGluIGEgc3BlY2lmaWVkIGNsdXN0ZXIgY29tcGFyZWQgdG8gYWxsIG90aGVyIGNlbGxzLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QgY29udGFpbmluZyB0aGUgZ2VuZSBleHByZXNzaW9uIGRhdGEgYW5kIGNsdXN0ZXIgaWRlbnRpdGllcy4KKmlkZW50LjEgPSAiMCIqOiBTcGVjaWZpZXMgdGhlIGNsdXN0ZXIgb2YgaW50ZXJlc3QgZm9yIHdoaWNoIG1hcmtlciBnZW5lcyBhcmUgdG8gYmUgaWRlbnRpZmllZC4gSW4gdGhpcyBjYXNlLCBjbHVzdGVyICIwIi4KKm1heC5jZWxscy5wZXIuaWRlbnQgPSA1MDAqOiBMaW1pdHMgdGhlIG51bWJlciBvZiBjZWxscyB0byBiZSB1c2VkIGZyb20gZWFjaCBjbHVzdGVyIGZvciB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgdG8gNTAwLiBUaGlzIGNhbiBoZWxwIHRvIHNwZWVkIHVwIHRoZSBjb21wdXRhdGlvbi4KCgpgYGB7cn0KbWFya2VycyA8LSBGaW5kTWFya2VycyhzZXVyYXRfQ1JDMiwgaWRlbnQuMT0iMCIsIG1heC5jZWxscy5wZXIuaWRlbnQ9NTAwKQpgYGAKCmBgYHtyfQpoZWFkKG1hcmtlcnMpCmBgYAoKV2UgY2FuIHZpc3VhbGlzZSBleHByZXNzaW9uIG9mIGNsdXN0ZXIgc3BlY2lmaWMgbWFya2VycyB1c2luZyBmZWF0dXJlIHBsb3RzCmBgYHtyfQpGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIkNEM0UiLCBsYWJlbD1ULCByZXBlbD1UKSsgc2NhbGVfY29sb3JfdmlyaWRpc19jKGRpcmVjdGlvbj0tMSkgI3QgY2VsbApGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIk1TNEExIiwgbGFiZWw9VCwgcmVwZWw9VCkrICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoZGlyZWN0aW9uPS0xKSAjQiBjZWxsCkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiQ0VBQ0FNNSIsIGxhYmVsPVQsIHJlcGVsPVQpKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoZGlyZWN0aW9uPS0xKSAjY29sb3JlY3RhbCBjYW5jZXIgYW5kIG5vbi1zbWFsbC1jZWxsIGx1bmcgY2FuY2VyCkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiS0lUIiwgbGFiZWw9VCwgcmVwZWw9VCkrIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhkaXJlY3Rpb249LTEpCiNoZSBLSVQgZ2VuZSBpcyBhIGNlbGwgc3VyZmFjZSBtYXJrZXIgYW5kIHByZWRpY3RpdmUgYmlvbWFya2VyIHRoYXQgY2FuIGJlIHVzZWQgZm9yIGEgdmFyaWV0eSBvZiBwdXJwb3NlcywgaW5jbHVkaW5nOiAKI2FuY2VyIGRpYWdub3NpcwojS0lUIGdlbmUgbXV0YXRpb25zIGNhbiBiZSB1c2VkIHRvIGRpYWdub3NlLCBwcmVkaWN0LCBhbmQgcHJvdmlkZSBwcm9nbm9zdGljIGluZm9ybWF0aW9uIGZvciBjZXJ0YWluIGNhbmNlcnMsIHN1Y2ggYXMgYWN1dGUgbXllbG9pZCBsZXVrZW1pYSAoQU1MKSwgbWVsYW5vbWEsIGdhc3Ryb2ludGVzdGluYWwgc3Ryb21hbCB0dW1vcnMgKEdJU1QpLCBhbmQgc3lzdGVtaWMgbWFzdG9jeXRvc2lzIChTTSkKCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJDRDNFIiwgc2l6ZT0uNSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJNUzRBMSIsIHNpemU9LjUpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiQ0VBQ0FNNSIsIHNpemU9LjUpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiS0lUIiwgc2l6ZT0uNSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYApPciwgYXMgaW4gb3VyIHNlcXVlbmNpbmcgU1QgdHV0b3JpYWwsIGRldGVjdCBhbmQgdmlzdWFsaXNlIHRvcCBtYXJrZXJzIGZvciBldmVyeSBjbHVzdGVyLgpgYGB7cn0KbWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhzZXVyYXRfQ1JDMiwgbWF4LmNlbGxzLnBlci5pZGVudCA9IDUwMCkKYGBgCgpgYGB7cn0KaGVhZChtYXJrZXJzKQpgYGAKCnNjQ3VzdG9taXplIHBhY2thZ2UgcHJvdmlkZXMgYSBjb252ZW5pZW50IGhlbHBlciBmdW5jdGlvbiwgKkV4dHJhY3RfVG9wX01hcmtlcnMqLCB0byBleHRyYWN0IHRoZSB0b3AgbWFya2VyIGdlbmVzIGZvciBlYWNoIGNsdXN0ZXIgZnJvbSB0aGUgb3V0cHV0IG9mICpGaW5kQWxsTWFya2VycyouIFRoaXMgZnVuY3Rpb24gc2ltcGxpZmllcyB0aGUgcHJvY2VzcyBvZiBpZGVudGlmeWluZyBhbmQgcmV0cmlldmluZyB0aGUgbW9zdCBzaWduaWZpY2FudCBtYXJrZXIgZ2VuZXMgZm9yIGFuYWx5c2lzIGFuZCB2aXN1YWxpc2F0aW9uLgoKSW4gdGhpcyBjYXNlLCB3ZSBhcmUgZXh0cmFjdGluZyB0aGUgdG9wIGZpdmUgbWFya2VycyBwZXIgY2x1c3Rlci4KCmBgYHtyfQp0b3AgPC0gRXh0cmFjdF9Ub3BfTWFya2VycyhtYXJrZXJzLCBudW1fZ2VuZXMgPSA1LCBuYW1lZF92ZWN0b3IgPSBGQUxTRSwgbWFrZV91bmlxdWUgPSBUUlVFKQp0b3AKYGBgCgoqQ2x1c3RlcmVkX0RvdFBsb3QqIGZ1bmN0aW9uIGZyb20gdGhlICpzY0N1c3RvbWl6ZSogcGFja2FnZSBwcm92aWRlcyBhIGNvbnZlbmllbnQgYW5kIHZpc3VhbGx5IGFwcGVhbGluZyB3YXkgdG8gZGlzcGxheSBleHByZXNzaW9uIHBhdHRlcm5zIG9mIHRvcCBtYXJrZXIgZ2VuZXMgYWNyb3NzIGNsdXN0ZXJzIHVzaW5nIGEgZG90IHBsb3QuIFRoaXMgZnVuY3Rpb24gbm90IG9ubHkgcGxvdHMgdGhlIGV4cHJlc3Npb24gZGF0YSBidXQgYWxzbyBjbHVzdGVycyB0aGUgZ2VuZXMgYW5kIGdyb3VwcyBmb3IgZW5oYW5jZWQgdmlzdWFsIGludGVycHJldGF0aW9uLiBUaGlzIGlzIGFuIGFsdGVybmF0aXZlIHRvIFNldXJhdCAqRG90UGxvdCogZnVuY3Rpb24uIAoKKmsgPSAxOCo6IERldGVybWluZXMgdGhlIG51bWJlciBvZiBjbHVzdGVycyBmb3IgdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIGdlbmVzIHRvIGVuaGFuY2UgdmlzdWFsIHNlcGFyYXRpb24gb2YgZXhwcmVzc2lvbiBwYXR0ZXJucy4gCgpXZSBjYW4gc2VlIHRoYXQgbW9zdCBjbHVzdGVycyBoYXZlIHVuaXF1ZSBtYXJrZXJzLCB3aGljaCBzdWdnZXN0cyB0aGUgZGF0YXNldCBpcyBub3Qgb3Zlci1jbHVzdGVyZWQuCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9N30KQ2x1c3RlcmVkX0RvdFBsb3Qoc2V1cmF0X0NSQzIsIGZlYXR1cmVzID0gdG9wLCBrPTE4KQpgYGAKCioqQWRkaXRpb25hbCBTcGF0aWFsIFZpc3VhbGlzYXRpb25zKioKClRoZSByZXNvbHV0aW9uIG9mICppbiBzaXR1KiBkYXRhc2V0cyBpcyB0eXBpY2FsbHkgdmVyeSBoaWdoIGFuZCBzbyBpdCBjYW4gYmUgZGlmZmljdWx0IHRvIHZpc3VhbGlzZSBldmVyeXRoaW5nIGluIG9uZSBwbG90LiBCZWxvdywgd2Ugd2lsbCBleHBsb3JlIGRpZmZlcmVudCB2aXN1YWxpc2F0aW9ucyB0aGF0IGNhbiBoZWxwIHVucGljayBhbmQgdW5kZXJzdGFuZCB0aGUgZGF0YSBhIGJpdCBiZXR0ZXIuIAoKClRvIGJldHRlciB2aXN1YWxpc2Ugc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgY2x1c3RlcnMsIHNvbWV0aW1lcyBpdCBjYW4gYmUgdXNlZnVsIHRvIHN1YnNldCBvbmx5IGNlcnRhaW4gZ3JvdXBzIHRvIHJlZHVjZSBjcm93ZGluZy4gIEhlcmUsIHdlIHNwZWNpZmljYWxseSBvbmx5IHZpc3VhbGlzaW5nIHR3byBzZWxlY3RlZCBjbHVzdGVycy4gCgoqV2hpY2hDZWxscyo6IElkZW50aWZpZXMgY2VsbHMgYmFzZWQgb24gc3BlY2lmaWVkIGNyaXRlcmlhLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QuCipleHByZXNzaW9uID0gc2V1cmF0X2NsdXN0ZXJzICVpbiUgYygwLCA1KSo6IExvZ2ljYWwgZXhwcmVzc2lvbiB0byBzZWxlY3QgY2VsbHMgYmVsb25naW5nIHRvIGNsdXN0ZXJzIDAgYW5kIDUuCgoKKipUaGlzIHdvcmtzIHdpdGggKkltYWdlRmVhdHVyZVBsb3QqIHRvby4gVHJ5IGl0IHdpdGggc29tZSBnZW5lcyEqKgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBjZWxscz1XaGljaENlbGxzKHNldXJhdF9DUkMyLCBleHByZXNzaW9uID0gc2V1cmF0X2NsdXN0ZXJzICVpbiUgYygwLCA1KSkpCmBgYAoKU29tZXRpbWVzLCBpdCBjYW4gYmUgdXNlZnVsIHRvIGNyZWF0ZSBhZGRpdGlvbmFsIGZpZWxkcyBvZiB2aWV3IG9mIHRoZSBkYXRhIC0gZm9yIGV4YW1wbGUsIHpvb21zIG9mIHNwZWNpZmljIHJlZ2lvbnMuIApGaXJzdCwgbGV0J3MgbG9vayBhdCB0aGUgY29vcmRpbmF0ZSBzeXN0ZW0gYnkgcGxvdHRpbmcgdGhlIGRhdGEgYW5kIHR1cm5pbmcgb24gdGhlIHBsb3R0aW5nIG9mIHRoZSBheGVzLCB3aGljaCBhcmUgb2ZmIGJ5IGRlZmF1bHQgdG8gY3JlYXRlIG5pY2VyIGxvb2tpbmcgcGxvdHMuIAoKVGhpcyBnaXZlcyB1cyBhIHJvdWdoIGlkZWEgb24gd2hlcmUgaW4gdGhlIGNvb3JkaW5hdGUgc3lzdGVtIHRvIGNyZWF0ZSBhbnkgc3Vic2V0cyBvciB6b29tcyBvZiB0aGUgZGF0YS4KCkZvciBleGFtcGxlLCBpZiB3ZSB3YW50IHRvIHpvb20gaW4gb24gdGhlIGZvbGxpY2xlIGluIHRoZSB0b3AgcmlnaHQgY29ybmVyLCB3ZSBjYW4gc2VlIHRoYXQgaXQgbGllcyByb3VnaGx5IGJldHdlZW4gNDAwMC01MDAwIGFuZCA4MDAwLTkwMDAgY29vcmRpbmF0ZSByZWdpb25zLiAKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGF4ZXMgPSBUKQpgYGAKU28sIGxldCdzIGNyZWF0ZSBhIG5ldyBGT1Ygd2l0aCB0aGVzZSBjb29yZGluYXRlcy4gRm9yIHRoaXMsIHdlIGNhbiB1c2UgdGhlICpDcm9wKiBmdW5jdGlvbi4gCgoqc2V1cmF0W1siQ09MT04iXV0qOiBUaGUgc3BhdGlhbCBhc3NheSB0byBiZSBjcm9wcGVkLgoqeCA9IGMoNDIwMCwgNTAwMCkqOiBUaGUgeC1heGlzIHJhbmdlIGZvciB0aGUgY3JvcC4KKnkgPSBjKDgwMDAsIDg4MDApKjogVGhlIHktYXhpcyByYW5nZSBmb3IgdGhlIGNyb3AuCipjb29yZHMgPSAicGxvdCIqOiBTcGVjaWZpZXMgdGhlIGNvb3JkaW5hdGUgc3lzdGVtIHRvIHVzZSAodHlwaWNhbGx5ICJwbG90IiBmb3Igc3BhdGlhbCBjb29yZGluYXRlcykuCgoqc2V1cmF0W1siUk9JMSJdXSA8LSBjcm9wcGVkKjogQWRkcyB0aGUgY3JvcHBlZCByZWdpb24gYXMgYSBuZXcgRk9WIG5hbWVkICJST0kxIiBpbiB0aGUgU2V1cmF0IG9iamVjdC4gVGhpcyBjb3VsZCBiZSBhIG1vcmUgaW5mb3JtYXRpdmUgbmFtZSwgYnV0IGF2b2lkIHVzaW5nIHVuZGVyc2NvcmVzIQoKYGBge3J9CmNyb3BwZWQgPC0gQ3JvcChzZXVyYXRbWyJDT0xPTiJdXSwgeCA9IGMoNDIwMCwgNTAwMCksIHkgPSBjKDgwMDAsIDg4MDApLCBjb29yZHMgPSAicGxvdCIpCnNldXJhdFtbIlJPSTEiXV0gPC0gY3JvcHBlZApgYGAKTm93IHdlIGNhbiBsaW1pdCBvdXIgdmlzdWFsaXNhdGlvbnMganVzdCB0byB0aGlzIHJlZ2lvbiBieSBzcGVjaWZ5aW5nIHRoZSBuYW1lIG9mIHRoZSBuZXcgRk9WIGFzIGFuICJmb3YiIGFyZ3VlbWVudC4gCgpBcyB3ZSBhcmUgem9vbWluZyBpbiBjbG9zZXIgdG8gdGhlIHRpc3N1ZSwgd2UgY2FuIGFsc28gc3dpdGNoIGZyb20gcGxvdHRpbmcgY2VsbCBjZW50cm9pZHMgKGkuZS4gZG90cykgYnkgZGVmYXVsdCB0byB2aXN1YWxpc2luZyBjZWxsIHNlZ21lbnRhdGlvbiBib3VuZGFyaWVzLiBQbG90dGluZyBjZWxsIGJvdW5kYXJ5IHBvbHlnb25zIGZvciBsYXJnZSBGT1ZzIGNhbiBiZSBxdWl0ZSB0aW1lIGNvbnN1bWluZywgYW5kIGRvZXNuJ3QgcHJvdmlkZSBtdWNoIG1vcmUgZGV0YWlsIG9uIGEgZnVsbHkgem9vbWVkLW91dCB2aWV3LiAKCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04fQpJbWFnZURpbVBsb3Qoc2V1cmF0LCBmb3Y9IlJPSTEiLCBib3VuZGFyaWVzPSJzZWdtZW50YXRpb24iLCBib3JkZXIuY29sb3IgPSAiYmxhY2siICkKYGBgCldlIGNhbiB2aXN1YWxpc2UgZ2VuZSBleHByZXNzaW9uIG9yIG90aGVyIGNvbnRpbm91cyB2YXJpYWJsZSBvbiB0aGUgbmV3IEZPViBhcyBiZWZvcmUuCgpGb3IgZXhhbXBsZSwgaGVyZSB3ZSBoYXZlIE1TNEExL0NEMjAgZXhwcmVzc2lvbiwgd2hpY2ggaXMgYSBCLUNlbGwgbWFya2VyLiBXZSBjYW4gc2VlIGl0IHF1aXRlIG5pY2VseSBsaW1pdGVkIHRvIHRoZSBseW1waG9pZCBmb2xsaWNsZS4gCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdCwgIk1TNEExIiwgZm92PSJST0kxIiwgYm91bmRhcmllcz0ic2VnbWVudGF0aW9uIiAsIGJvcmRlci5jb2xvciA9ICJibGFjayIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCldlIGNhbiBhbHNvIG92ZXJsYXkgdGhlIGNvb3JkaW5hdGVzIG9mIGluZGl2aWR1YWwgbW9sZWN1bGVzIHRvIHRoZSBwbG90LiBGb3IgZXhhbXBsZSwgaGVyZSB3ZSBhcmUgYWRkZWQgc29tZSBtb3JlIFQtY2VsbCBhbmQgQi1jZWxsIHNwZWNpZmljIG1hcmtlcnMuIAoKVGhpcyB2aXN1YWxpc2F0aW9uIGNhbiBiZSB1c2VmdWwgYmVjYXVzZSBtb2xlY3VsZXMgYXJlIHN0b3JlZCBpbmRlcGVuZGVudGx5IG9mIGNlbGxzIGFuZCBjZWxsIGJvdW5kYXJpZXMgaW4gU2V1cmF0LiBUaGVyZWZvcmUsIGlmIHRoZXJlIGFyZSByZWdpb25zIHdoZXJlIGNlbGwgc2VnbWVudGF0aW9uIGlzIG5vdCBnb29kLCBvciBpZiBjZWxscyB3ZXJlIGZpbHRlcmVkIG91dCBmcm9tIGNsdXN0ZXJpbmcgYW5hbHlzaXMgZHVlIHRvIHRoZWlyIGxvdyBxdWFsaXR5LCB0aGUgbW9sZWN1bGVzIHdpbGwgcmVtYWluIGFuZCBjYW4gc3RpbGwgYmUgdmlzdWFsaXNlZCB0aGlzIHdheS4KCkZvciBleGFtcGxlLCBoZXJlIHdlIGNhbiBzZWUgdGhlcmUgYXJlIGEgZmV3IG1vbGVjdWxlcyBvZiBDWENSNSBkZXRlY3RlZCBvdXRzaWRlIG9mIGNlbGx1bGFyIGJvdW5kYXJpZXMuIAoKYGBge3J9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0LCAiTVM0QTEiLCBmb3Y9IlJPSTEiLCBib3VuZGFyaWVzPSJzZWdtZW50YXRpb24iLCBtb2xlY3VsZXM9YygiQ1hDUjUiLCAiRk9YUDMiKSwgbW9scy5zaXplID0gLjUsIGJvcmRlci5jb2xvciA9ICJibGFjayIgKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKYGBgCioqQ2VsbCBUeXBlIElkZW50aWZpY2F0aW9uKioKCllvdSBjYW4gbWFudWFsbHkgYW5ub3RhdGUgeW91ciBjZWxsIGNsdXN0ZXJzLCBvciB5b3UgY2FuIGNsYXNzaWZ5IHRoZW0gdXNpbmcgYSByZWZlcmVuY2Ugc2luZ2xlLWNlbGwgZGF0YXNldC4gVGhpcyBwcm9jZXNzIGlzIHNpbXBsZXIgdGhhbiBmb3IgVmlzaXVtIGRhdGEgYmVjYXVzZSBvdXIgZGF0YSBpcyBhdCB0aGUgc2luZ2xlLWNlbGwgbGV2ZWwsIGVzdGFibGlzaGluZyBhIG9uZS10by1vbmUgcmVsYXRpb25zaGlwIHdpdGhvdXQgdGhlIG5lZWQgZm9yIHNwb3QgZGVjb252b2x1dGlvbi4KCkhvd2V2ZXIsIG91ciB0cmFuc2NyaXB0b21lIGlzIG1vcmUgbGltaXRlZCBoZXJlLCBhbmQgc29tZSBjZWxsIHR5cGVzIG1heSBub3QgYmUgd2VsbCByZXByZXNlbnRlZC4gQWRkaXRpb25hbGx5LCBvdXIgc2luZ2xlLWNlbGwgcmVmZXJlbmNlIG1pZ2h0IGJlIG1pc3Npbmcgc29tZSBjZWxsIHR5cGVzIHRoYXQgYXJlIG5vdCB3ZWxsIGNhcHR1cmVkIGJ5IGRyb3BsZXQtYmFzZWQgdGVjaG5vbG9naWVzIGJ1dCBhcmUgcHJlc2VudCBpbiBvdXIgdGlzc3VlIGRhdGEuCgpJbiB0aGlzIGV4YW1wbGUsIHdlIHdpbGwgdXNlIGEgc2luZ2xlLWNlbGwgcmVmZXJlbmNlIGRhdGFzZXQgdGhhdCB3ZSBwcmVwYXJlZCBlYXJsaWVyLgoKV2Ugd2lsbCBzdGFydCBieSByZWFkaW5nIGluIHRoZSBzZXVyYXQgUkRTIGZpbGUuCmBgYHtyfQpyZWYgPC0gcmVhZFJEUygiL3Byb2plY3Qvc2hhcmVkL3NwYXRpYWxfZGF0YV9jYW1wL2RhdGFzZXRzL1NJTkdMRV9DRUxMX1JFRkVSRU5DRVMvQ09MT05fSENfNUtfQ0VMTFMuUkRTIikKYGBgCgpFeGFtaW5lIHRoZSBvYmplY3Q6CmBgYHtyfQpyZWYKYGBgCkFuZCBwbG90IHRoZSBwcmUtY29tcHV0ZWQgY2VsbCBjbHVzdGVycy4gV2UgY2FuIHNlZSB0aGF0IGhlcmUgd2UgaGF2ZSBxdWl0ZSBoaWdoIGxldmVsIGFubm90YXRpb24uIApgYGB7cn0KRGltUGxvdChyZWYpCmBgYApXZSB3YW50IHRvIGV2YWx1YXRlIGhvdyBtdWNoIHN0cnVjdHVyYWwgaW5mb3JtYXRpb24gaXMgbG9zdCBpbiBzaW5nbGUtY2VsbCBkYXRhIHdoZW4gbGltaXRpbmcgb3Vyc2VsdmVzIHRvIHRoZSB0YXJnZXRlZCBnZW5lIHNldC4gQWNjdXJhdGUgY2x1c3RlciBwcmVkaWN0aW9uIGlzIGNoYWxsZW5naW5nIGlmIHRoZSBjdXJyZW50IGdlbmUgc2V0IGRvZXMgbm90IGFkZXF1YXRlbHkgaWRlbnRpZnkgdGhlbS4gVG8gZG8gdGhpcywgd2Ugd2lsbCBxdWlja2x5IHJlLWVtYmVkZCB0aGUgZGF0YSB1c2luZyBvbmx5IHRoZSBnZW5lcyBwcmVzZW50IGluIG91ciBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGFuZCBrZWVwIHRoZSBvcmlnaW5hbCBjbHVzdGVyIGFubm90YXRpb25zIGRlcml2ZWQgZnJvbSB1bmJpYXNlZCBkYXRhLgoKSW4gdGhpcyBleGFtcGxlLCB3ZSBjYW4gb2JzZXJ2ZSB0aGF0IHRoZSBsaW1pdGVkIGdlbmUgc2V0IGRvZXMgYSByZWFzb25hYmx5IGdvb2Qgam9iIGF0IGRpc3Rpbmd1aXNoaW5nIG1ham9yIGNlbGwgcG9wdWxhdGlvbnMuIEhvd2V2ZXIsIGl0IHN0cnVnZ2xlcyB0byBkaWZmZXJlbnRpYXRlIGJldHdlZW4gc2ltaWxhciBjZWxsIHR5cGVzLCBzdWNoIGFzIG15b2ZpYnJvYmxhc3RzIGFuZCBmaWJyb2JsYXN0cywgYXMgZWZmZWN0aXZlbHkgYXMgYmVmb3JlLgoKYGBge3J9CnJlZiA8LSBTQ1RyYW5zZm9ybShyZWYsIHJlc2lkdWFsLmZlYXR1cmVzID1yb3duYW1lcyhzZXVyYXRfQ1JDMikpCnJlZiA8LSBSdW5QQ0EocmVmKQpyZWYgPC0gUnVuVU1BUChyZWYsIGRpbXM9MToyMCkKRGltUGxvdChyZWYsIGxhYmVsPVQsIHJlcGVsPVQpCmBgYApJZiB3ZSB2aXN1YWxpc2UgdGhlIHNwZWNpZmljaXR5IG9mIHRoZSBnZW5lIHBhbmVsIGFjcm9zcyBvdXIgc2luZ2xlIGNlbGwgcmVmZXJlbmNlIGNsdXN0ZXJzLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHBhbmVsIGNvdmVyYWdlIGlzIG1haW5seSBjb25jZW50cmF0ZWQgYWNyb3NzIGVwaXRoZWxpYWwgY2VsbHMgYW5kIFQtQ2VsbHMgYW5kIG90aGVyIGltbXVuZSBjZWxscywgd2l0aCBmZXcgc3BlY2lmaWMgbWFya2VycyBleHByZXNzZWQgYnkgc3Ryb21hbCBjZWxscy4gCmBgYHtyfQpwcyA8LSBBZ2dyZWdhdGVFeHByZXNzaW9uKHJlZiwgZmVhdHVyZXMgPSByb3duYW1lcyhzZXVyYXQpLCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiLCBhc3NheXM9IlJOQSIsIHJldHVybi5zZXVyYXQgPSBUKQpwcyA8LSBTY2FsZURhdGEocHMsIGZlYXR1cmVzPXJvd25hbWVzKHBzKSkKcGhlYXRtYXAoTGF5ZXJEYXRhKHBzLCBsYXllcj0ic2NhbGUuZGF0YSIpLCBzaG93X3Jvd25hbWVzID0gRikKYGBgCgoKTmV4dCwgd2UgY2FuIHVzZSB0aGUgc3RhbmRhcmQgU2V1cmF0IGludGVncmF0aW9uIGFuZCBjcm9zcy1jbGFzc2lmaWNhdGlvbiB3b3JrZmxvdyB0byB0cmFuc2ZlciBzaW5nbGUtY2VsbCBkZXJpdmVkIGxhYmVscyB0byBvdXIgc3BhdGlhbCBvYmplY3QuCgpCcmllZmx5LCB0aGUgZmlyc3QgZnVuY3Rpb24gaWRlbnRpZmllcyBhbmNob3JzIGJldHdlZW4gdGhlIHJlZmVyZW5jZSBzaW5nbGUtY2VsbCBkYXRhc2V0IChyZWYpIGFuZCB0aGUgcXVlcnkgc3BhdGlhbCBkYXRhc2V0IChzZXVyYXQpLiBBbmNob3JzIGFyZSBwYWlycyBvZiBjZWxscyB0aGF0IGFyZSBjb25zaWRlcmVkIHNpbWlsYXIgYmV0d2VlbiB0aGUgZGF0YXNldHMuIFRoZSAqbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiU0NUIiogc3BlY2lmaWVzIHRoYXQgKlNDVHJhbnNmb3JtKiBub3JtYWxpemF0aW9uIHNob3VsZCBiZSB1c2VkLgoKVGhlIHNlY29uZCBzdGVwIHRyYW5zZmVycyB0aGUgY2VsbCB0eXBlIGxhYmVscyBmcm9tIHRoZSByZWZlcmVuY2UgZGF0YXNldCB0byB0aGUgcXVlcnkgZGF0YXNldC4gVGhlIGFuY2hvcnNldCBhcmd1bWVudCBzcGVjaWZpZXMgdGhlIGFuY2hvcnMgZm91bmQgaW4gdGhlIHByZXZpb3VzIHN0ZXAuIFRoZSAqcmVmZGF0YSA9IHJlZiRDZWxsVHlwZSogYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBjZWxsIHR5cGUgbGFiZWxzIGZyb20gdGhlIHJlZmVyZW5jZSBkYXRhc2V0IHRvIGJlIHRyYW5zZmVycmVkLiBUaGUgKnByZWRpY3Rpb24uYXNzYXkgPSBUUlVFKiBhcmd1bWVudCBpbmRpY2F0ZXMgdGhhdCB0aGUgdHJhbnNmZXJyZWQgbGFiZWxzIHNob3VsZCBiZSBzdG9yZWQgaW4gYSBuZXcgYXNzYXkgaW4gdGhlIHF1ZXJ5IGRhdGFzZXQuIFRoZSAqd2VpZ2h0LnJlZHVjdGlvbiA9IHNldXJhdFtbInBjYSJdXSogYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdG8gYmUgdXNlZCBmb3Igd2VpZ2h0aW5nIHRoZSB0cmFuc2ZlciwgYW5kICpkaW1zID0gMTozMCogc3BlY2lmaWVzIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB0byB1c2UuCgoKYGBge3J9CmFuY2hvcnMgPC0gRmluZFRyYW5zZmVyQW5jaG9ycyhyZWZlcmVuY2UgPSByZWYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBzZXVyYXRfQ1JDMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJTQ1QiKQoKc2V1cmF0X0NSQzIgPC0gVHJhbnNmZXJEYXRhKGFuY2hvcnNldCA9IGFuY2hvcnMsIAogICAgICAgICAgICAgICAgICAgICAgIHJlZmRhdGEgPSByZWYkQ2VsbFR5cGUsIAogICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3Rpb24uYXNzYXkgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgIHdlaWdodC5yZWR1Y3Rpb24gPSBzZXVyYXRfQ1JDMltbInBjYSJdXSwgCiAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBzZXVyYXRfQ1JDMiwgCiAgICAgICAgICAgICAgICAgICAgICAgZGltcz0xOjMwKQoKYGBgCgpVbmZvcnR1bmF0ZWx5LCB0aGUgcHJlZGljdGVkIGxhYmVscyBhbmQgc3BhdGlhbCBjbHVzdGVycyBkbyBub3QgY29ycmVzcG9uZCBjbGVhcmx5IGluIGFsbCBjYXNlcy4gVGhpcyBkaXNjcmVwYW5jeSBpcyBwYXJ0aWN1bGFybHkgZXZpZGVudCBpbiB0aGUgbWlkZGxlIHJlZ2lvbnMgb2YgdGhlIFVNQVAsIHdoZXJlIG1hbnkgY2VsbHMgYXJlIHByZWRpY3RlZCBhcyBlcGl0aGVsaWFsIGNlbGxzIC0gcHJvYmFibHkgaW5jb3JyZWN0bHkhCgpIb3cgdG8gaW1wcm92ZSB0aGlzPwoKKipFbnN1cmUgR29vZCBSZXByZXNlbnRhdGlvbiBvZiBDZWxsIFR5cGUgTWFya2VycyBpbiAqaW4gc2l0dSogVGFyZ2V0IFBhbmVsKioKTW9zdCBjcml0aWNhbGx5LCBiZWZvcmUgdW5kZXJ0YWtpbmcgYW55IGV4cGVyaW1lbnRzIHlvdSB3YW50IHRvIGVuc3VyZSB0aGF0IHRoZXJlIGlzIGdvb2QgcmVwcmVzZW50YXRpb24gb2YgYWxsIGNlbGwgdHlwZXMgaW4geW91ciB0YXJnZXQgcGFuZWwgLSBpbiB0aGlzIGNhc2UsIHRoZXJlIGlzIG5vdCBtdWNoIHRvIGJlIGRvbmUgYXMgdGhlIGRhdGEgaGFzIGFscmVhZHkgYmVlbiBnZW5lcmF0ZWQuIAoKKipSZXZpZXcgYW5kIFJlZmluZSBSZWZlcmVuY2UgRGF0YToqKgpFbnN1cmUgdGhhdCB0aGUgcmVmZXJlbmNlIHNpbmdsZS1jZWxsIGRhdGFzZXQgaXMgY29tcHJlaGVuc2l2ZSBhbmQgYWNjdXJhdGVseSBhbm5vdGF0ZWQuIElmIGNlcnRhaW4gY2VsbCB0eXBlcyBhcmUgbm90IHdlbGwgcmVwcmVzZW50ZWQgb3IgYW5ub3RhdGVkIGluIHRoZSByZWZlcmVuY2UgZGF0YXNldCwgaXQgY2FuIGxlYWQgdG8gbWlzY2xhc3NpZmljYXRpb24uCgoqKkluY3JlYXNlIHRoZSBOdW1iZXIgb2YgRGltZW5zaW9uczoqKgpJbmNyZWFzaW5nIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB1c2VkIGluIHRoZSBVTUFQIGFuZCBQQ0Egc3RlcHMgbWlnaHQgY2FwdHVyZSBtb3JlIHZhcmlhbmNlIGluIHRoZSBkYXRhLCBsZWFkaW5nIHRvIGJldHRlciBsYWJlbCB0cmFuc2Zlci4KCioqRmlsdGVyIGFuZCBQcmVwcm9jZXNzIERhdGE6KioKRmlsdGVyaW5nIG91dCBsb3ctcXVhbGl0eSBjZWxscyBvciBnZW5lcyBhbmQgcGVyZm9ybWluZyBhZGRpdGlvbmFsIHByZXByb2Nlc3Npbmcgc3RlcHMgY2FuIGVuaGFuY2UgdGhlIGFjY3VyYWN5IG9mIHRoZSB0cmFuc2ZlciBhbmNob3JzIGFuZCwgY29uc2VxdWVudGx5LCB0aGUgbGFiZWwgcHJlZGljdGlvbnMuIAoKKipNYW51YWxseSBBbm5vdGF0ZSBvciBDb3JyZWN0IFByZWRpY3Rpb25zOioqCkluIGNhc2VzIHdoZXJlIGF1dG9tYXRpYyBsYWJlbCB0cmFuc2ZlciBpcyBpbnN1ZmZpY2llbnQsIGNvbnNpZGVyIG1hbnVhbGx5IGFubm90YXRpbmcgb3IgY29ycmVjdGluZyB0aGUgcHJlZGljdGlvbnMgZm9yIGNyaXRpY2FsIHJlZ2lvbnMgdG8gZW5zdXJlIGFjY3VyYWN5LgoKCmBgYHtyfQpEaW1QbG90KHNldXJhdF9DUkMyLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWQiLGxhYmVsID0gVCkKRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJwcmVkaWN0ZWQuaWQuc2NvcmUiKQpgYGAKQXMgYmVmb3JlLCB3ZSBjYW4gYWxzbyB2aXN1YWxpc2UgdGhlIHByZWRpY3RlZCBjZWxsIGxhYmVscyBpbiB0aXNzdWUgc3BhY2UuCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZCIpCmBgYApJbiBsaW5lIHdpdGggbm9uLXNwZWNpZmljIHByZWRpY3Rpb25zLCB3ZSBjYW4gYWxzbyBzZWUgdGhhdCB0aGUgcHJlZGljdGlvbiBzY29yZSBhY3Jvc3MgdGhlc2UgYXJlYXMgaXMgbG93ZXIuIAoKT3V0c2lkZSBvZiBzdHJvbWFsIGNlbGxzLCB3ZSBjYW4gYWxzbyBzZWUgdGhhdCBwcmVkaWN0aW9uIHByb2JhYmlsaXR5IGNhbiBiZSBsb3cgaW4gY2VsbHMgdGhhdCBlbWJlZGQgImJldHdlZW4iIGNsdXN0ZXJzLCBmb3IgZXhhbXBsZSBiZXR3ZWVuIGNvcmUgVC1DZWxscyBhbmQgQi1DZWxscywgdHdvIHBvcHVsYXRpb25zIHRoYXQgc2hvdWxkIGJlIGRpc3RpbmN0LiAKClRoaXMgaXMgb2Z0ZW4gdGhlIGNhc2Ugd2hlcmUgY2VsbCBzZWdtZW50YXRpb24gaXMgaW1wZXJmZWN0IGFuZCBwYXJ0aXRpb25zIHRyYW5zY3JpcHRzIGluIHN1Y2ggYSB3YXkgdGhhdCBpdCBnZW5lcmF0ZXMgImFydGlmaWNpYWwiIGRvdWJsZXRzIGJ5IHB1bGxpbmcgaW4gdHJhbnNjcmlwdHMgZnJvbSBhbiBhZGphY2VudCBjZWxsLiAKYGBge3J9CkZlYXR1cmVQbG90KHNldXJhdCwgInByZWRpY3RlZC5pZC5zY29yZSIpCmBgYApGb3IgZXhhbXBsZSwgaWYgd2UgdmlzdWFsaXNlIHRoZSBsaW5lYWdlIG1hcmtlcnMgZm9yIFQtQ2VsbHMgYW5kIEItQ2VsbHMsIHdlIGNhbiBzZWUgdGhhdCB0aGV5IGFyZSBvZnRlbiAiY28tZXhwcmVzc2VkIiBpbiB0aGUgc2FtZSBjZWxscyB3aGVuIGJpb2xvZ2ljYWxseSwgdGhleSBzaG91bGQgbm90IGJlLiAKClRoZSAqRmVhdHVyZVNjYXR0ZXIqIGZ1bmN0aW9uIGluIFNldXJhdCBpcyB1c2VkIHRvIGNyZWF0ZSBhIHNjYXR0ZXIgcGxvdCBzaG93aW5nIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgb2YgdHdvIGdlbmVzIGFjcm9zcyBhbGwgY2VsbHMuIFRoaXMgdmlzdWFsaXphdGlvbiBoZWxwcyB0byBpZGVudGlmeSBwb3RlbnRpYWwgY29ycmVsYXRpb25zIG9yIHBhdHRlcm5zIGJldHdlZW4gdGhlIHR3byBnZW5lcy4KCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0KRmVhdHVyZVNjYXR0ZXIoc2V1cmF0X0NSQzIsICJNUzRBMSIsICJDRDNEIiwgaml0dGVyPVQpCkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCBjKCJNUzRBMSIsICJDRDNEIikpCmBgYAoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCAgYm91bmRhcmllcz0ic2VnbWVudGF0aW9uIiwgYm9yZGVyLmNvbG9yID0gImJsYWNrIiApCmBgYAoKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIpCmBgYAoKYGBge3J9CkltYWdlRGltUGxvdChzZXVyYXRfQ1JDMiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkIikKYGBgCioqU3BhdGlhbCBOZWlnaGJvdXJob29kIEFuYWx5aXMqKgoKYGBge3J9CmNvb3JkcyA8LSBHZXRUaXNzdWVDb29yZGluYXRlcyhzZXVyYXRfQ1JDMiwgd2hpY2ggPSAiY2VudHJvaWRzIikKcm93bmFtZXMoY29vcmRzKSA8LSBjb29yZHMkY2VsbApuZWlnaGJvdXJzIDwtIEZpbmROZWlnaGJvcnMoYXMubWF0cml4KGNvb3Jkc1ssIGMoIngiLCAieSIpXSksIGsucGFyYW0gPSAyMCwgcmV0dXJuLm5laWdoYm9yPVRSVUUpCgpgYGAKQ29tcHV0aW5nIG5lYXJlc3QgbmVpZ2hib3JzCmBgYHtyfQpjZWxscyA8LSBXaGljaENlbGxzKHNldXJhdF9DUkMyLCBleHByZXNzaW9uPSBTQ1Rfc25uX3Jlcy4wLjIgPT0gMykKYWRqYWNlbnQgPC0gVG9wTmVpZ2hib3JzKG5laWdoYm91cnMsIGNlbGxzLCBuID0gMTApCgpJZGVudHMoc2V1cmF0X0NSQzIpIDwtICJPdGhlciBDZWxscyIKc2V1cmF0X0NSQzIgPC0gU2V0SWRlbnQoc2V1cmF0X0NSQzIsIGNlbGxzID0gYWRqYWNlbnQsICJBZGphY2VudCBDZWxscyIpCnNldXJhdF9DUkMyIDwtIFNldElkZW50KHNldXJhdF9DUkMyLCBjZWxscyA9IGNlbGxzLCAiQ2VsbHMgb2YgSW50ZXJlc3QiKQoKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyKQoKc2V1cmF0X0NSQzJbWyJncm91cDEiXV0gPC0gSWRlbnRzKHNldXJhdF9DUkMyKQpgYGAKKipGaW5kaW5nIFNwYXRpYWxseSBDb3JyZWxhdGVkIEdlbmVzKioKYGBge3J9Cm5laWdoYm91cnMgPC0gRmluZE5laWdoYm9ycyhhcy5tYXRyaXgoY29vcmRzWywgYygieCIsICJ5IildKSwgay5wYXJhbSA9IDUwKQptdCA8LSBMYXllckRhdGEoc2V1cmF0X0NSQzIsIGxheWVyID0gImNvdW50cyIsIGFzc2F5ID0gIlhFTklVTSIpCnN1bV9tdHggPC0gYXMubWF0cml4KG5laWdoYm91cnMkbm4gJSolIHQobXQpKQoKYGBgCgpXZSBjYW4gc3RvcmUgdGhlIG5laWdoYm91cmhvb2QtYWdncmVnYXRlZCB2YWx1ZXMgaW4gb3VyIFNldXJhdCBvYmplY3QgYXMgYSBzZXBhcmF0ZSBhc3NheSwgd2hpY2ggd2Ugd2lsbCBjYWxsICJORUlHSEJPVVJIT09ENTAiLiBXZSB0aGVuIG5vcm1hbGlzZSB0aGUgbWF0cml4LiAKYGBge3J9CnNldXJhdF9DUkMyW1siTkVJR0hCT1VSSE9PRDUwIl1dIDwtIENyZWF0ZUFzc2F5T2JqZWN0KHQoc3VtX210eCkpCnNldXJhdF9DUkMyIDwtIE5vcm1hbGl6ZURhdGEoc2V1cmF0X0NSQzIsIGFzc2F5ID0gIk5FSUdIQk9VUkhPT0Q1MCIpCgpgYGAKV2UgY2FuIHRoZW4gYXBwbHkgcXVpY2sgY29ycmVsYXRpb24gY2FsY3VsYXRpb25zIHRvIGlkZW50aWZ5IHNwYXRpYWxseSBjb3JyZWxhdGVkIGZlYXR1cmVzLiAKCgpgYGB7cn0KY29ycmdlbmVzIDwtIGNvcihhcy5tYXRyaXgodChMYXllckRhdGEoc2V1cmF0X0NSQzIsIGFzc2F5ID0gIk5FSUdIQk9VUkhPT0Q1MCIsIGxheWVyID0gImRhdGEiKSkpKQpkaWFnKGNvcnJnZW5lcykgPC0gMApoaWdoX2NvcnJfZ2VuZXMgPC0gd2hpY2gocm93TWF4cyhjb3JyZ2VuZXMpID4gLjcpCmRpYWcoY29ycmdlbmVzKSA8LSAxCmhlYXRtYXAgPC0gcGhlYXRtYXAoY29ycmdlbmVzW2hpZ2hfY29ycl9nZW5lcywgaGlnaF9jb3JyX2dlbmVzXSwgYm9yZGVyX2NvbG9yID0gTkEpCmBgYApgYGB7cn0KbW9kdWxlcyA8LSBjdXRyZWUoaGVhdG1hcCR0cmVlX3JvdywgNSkKbW9kdWxlcwpgYGAKTGV0cyB2aXN1YWxpemUgc29tZSBvZiB0aGUgZGV0ZWN0ZWQgc3BhdGlhbGx5IGNvLWxvY2FsaXppbmcgZ2VuZXMuIEZvciBleGFtcGxlLCAgbW9kdWxlIDIgZ2VuZXMgLSB3ZSBjYW4gc2VlIHRoYXQgQ0VBQ0FNNiBhbmQgQVFQOCBhcmUgc3BhdGlhbGx5IHNpbWlsYXIsIGJ1dCBub3QgbmVjZXNzYXJpbHkgYWx3YXlzIGV4cHJlc3NlZCBieSB0aGUgc2FtZSBjZWxscy4KYGBge3J9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJBS1I3QTMiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIkMxUUJQIikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJDRDI0IikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJBTlhBMSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCmBgYHtyfQpzZXVyYXRfQ1JDMiA8LSBBZGRNb2R1bGVTY29yZShzZXVyYXRfQ1JDMiwgZmVhdHVyZXM9c3BsaXQobmFtZXMobW9kdWxlcyksIG1vZHVsZXMpLCBhc3NheSA9ICJTQ1QiLCBuYmluPTMsIG5hbWUgPSAiTU9EIiApCmBgYApWaXN1YWxpc2luZyBtb2R1bGUgc2NvcmVzIC0gd2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgaWRlbnRpZmllZCBhIGdyb3VwIG9mIGdlbmVzIGNvLWxvY2FsaXNpbmcgYXQgdGhlIGJhc2Ugb2YgdGhlIGVwaXRoZWxpYWwgY3J5cHRzIChNT0QxKSBhbmQgYW5vdGhlciBtb2R1bGUgb2YgZ2VuZXMgY28tbG9jYWxpc2luZyBpbiBseW1waG9pZCBmb2xsaWNsZXMuCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9EMSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9EMiIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9EMyIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9ENCIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9ENSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKKipEZXRlY3RpbmcgQ2VsbHVsYXIgTmljaGVzKioKYGBge3J9Cm5laWdoYm91cnMgPC0gRmluZE5laWdoYm9ycyhhcy5tYXRyaXgoY29vcmRzWywgYygieCIsICJ5IildKSwgay5wYXJhbSA9IDEwMCkKZGlhZyhuZWlnaGJvdXJzJG5uKSA8LSAwICMgZG9udCBjb3VudCB0cmFuc2NyaXB0b21lIG9mIHRoZSBjZWxsIGl0c2VsZiwganVzdCBuZWlnaGJvdXJzCm10IDwtIExheWVyRGF0YShzZXVyYXRfQ1JDMiwgbGF5ZXIgPSAiY291bnRzIiwgYXNzYXkgPSAiWEVOSVVNIikKc3VtX210eCA8LSBhcy5tYXRyaXgobmVpZ2hib3VycyRubiAlKiUgdChtdCkpCmBgYAoKSG93IGlzIHRoaXMgdXNlZnVsPyBXZWxsLCBub3cgeW91IGNhbiBjbHVzdGVyIGNlbGxzIG5vdCBvbiB0aGVpciBnZW5lIGV4cHJlc3Npb24gdmFsdWVzLCBidXQgZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBvZiBzdXJyb3VuZGluZyBjZWxscy4gVGhpcyBlZmZlY3RpdmVseSBwYXJ0aXRpb25zIGNlbGxzIG5vdCBiYXNlZCBvbiB0aGVpciBpZGVudGl0eSwgYnV0IG9uIHRoZWlyIG1pY3JvLWVudmlyb25tZW50IQpVc2luZyB0aGlzIGFwcHJvYWNoLCB5b3UgY2FuIGlkZW50aWZ5IHRpc3N1ZSBuaWNoZXMKCkFsdGVybmF0aXZlIGFwcHJvYWNoZXMgLSB5b3UgY291bGQgY291bnQgY2VsbCB0eXBlcyByYXRoZXIgdGhhbiBnZW5lIGV4cHJlc3Npb24gdmFsdWVzLCBidXQgdGhhdCByZXF1aXJlcyB5b3UgdG8gaGF2ZSBmaW5hbGlzZWQgY2VsbCBhbm5vdGF0aW9uIGZvciB5b3VyIGRhdGFzZXQsIHdoaWNoIGlzIG5vdCBpZGVhbC4gU28sIHdlIGRvIHVuYmlhc2VkIHRyYW5zY3JpcHRvbWljcyBhcHByb2FjaC4gCgoqKkhvdyB3b3VsZCB5b3UgcnVuIHRoaXMgd2l0aCBjZWxsIHR5cGVzPyoqCmBgYHtyfQpzZXVyYXRfQ1JDMltbIk5FSUdIQk9VUkhPT0QxMDAiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QodChzdW1fbXR4KSkKRGVmYXVsdEFzc2F5KHNldXJhdF9DUkMyKSA8LSAiTkVJR0hCT1VSSE9PRDEwMCIKc2V1cmF0X0NSQzIgPC0gTm9ybWFsaXplRGF0YShzZXVyYXRfQ1JDMikKc2V1cmF0X0NSQzIgPC0gU2NhbGVEYXRhKHNldXJhdF9DUkMyLCBmZWF0dXJlcyA9IHJvd25hbWVzKHNldXJhdF9DUkMyKSkKc2V1cmF0X0NSQzIgPC0gUnVuUENBKHNldXJhdF9DUkMyLCBmZWF0dXJlcyA9IHJvd25hbWVzKHNldXJhdF9DUkMyKSkKc2V1cmF0X0NSQzIgPC0gRmluZE5laWdoYm9ycyhzZXVyYXRfQ1JDMiwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjEwKQpzZXVyYXRfQ1JDMiA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0X0NSQzIsIHJlc29sdXRpb24gPSAwLjEsIGNsdXN0ZXIubmFtZSA9ICJOaWNoZXMiKQoKYGBgCkxldHMgdmlzdWFsaXNlIHRoZSBkZXRlY3RlZCAibmljaGVzIi4gV2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgYWNoaWV2ZWQgYSBjb2Fyc2UgcGFydGlvbmluZyBvZiB0aGUgY2VsbHMgaW50byBjcnlwdCB0b3AsIG1pZC1jcnlwdCBhbmQgY3J5cHQtYmFzZSByZWdpb25zLCBhcyB3ZWxsIGFzIHNlZ21lbnRpbmcgb3V0IGZvbGxpY2xlcyBhbmQgc3ViLW11Y29zYWwgc3Ryb21hLgoKKipIb3cgd291bGQgeW91IHR3ZWFrIHRoZSBhYm92ZSBhcHByb2FjaCB0byBnZW5lcmF0ZSBtb3JlIG9yIGxlc3MgZ3JhbnVsYXIgbmljaGVzPyoqCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5ID0gIk5pY2hlcyIpCmBgYApXZSBjYW4gdGFidWxhdGUgb3VyIGRldGVjdGVkIG5pY2hlcyB3aXRoIHByZWRpY3RlZCBjZWxsIHR5cGUgbGFiZWxzIChvciBjbHVzdGVycykgdG8gdmlzdWFsaXNlIGVucmljaG1lbnQgb2YgZGlmZmVyZW50IGNlbGwgdHlwZXMgYWNyb3NzIHNwYXRpYWwgbmljaGVzLiAKCkZvciBleGFtcGxlLCBhcyBjb3VsZCBiZSBleHBlY3RlZCwgVC1DZWxscyBhbmQgQi1DZWxscyBlbnJpY2ggaW4gTmljaGUgMiAoZm9sbGljdWxhcikuCgpgYGB7cn0KY29tcCA8LSB0YWJsZShzZXVyYXRfQ1JDMiROaWNoZXMsIHNldXJhdF9DUkMyJHByZWRpY3RlZC5pZCkKcGhlYXRtYXAoY29tcCwgc2NhbGU9InJvdyIpCmBgYAoKYGBge3J9CmNvbXAgPC0gdGFibGUoc2V1cmF0X0NSQzIkTmljaGVzLCBzZXVyYXRfQ1JDMiRTQ1Rfc25uX3Jlcy4wLjYpCnBoZWF0bWFwKGNvbXAsIHNjYWxlPSJyb3ciKQpgYGAKYGBge3J9CnNhdmVSRFMoc2V1cmF0X0NSQzIsICJzZXVyYXRfQ1JDMl9hbm5vdC5SRFMiKQpgYGAKCgpjaGFuZ2UgdGhlIGNvbG9yCmBgYHtyfQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShzY2FsZXMpCmNlbGxfY29sb3VycyA8LSBjKCIjRjg3NjZEIiwgIiNEQjhFMDAiLCAiI0FFQTIwMCIsICIjNjRCMjAwIiwgIiMwMEJENUMiLCAiIzAwQzFBNyIsIAogICAgICAgICAgICAgICAgICAiIzAwQkFERSIsICIjMDBBNkZGIiwgIiNCMzg1RkYiLCAiI0VGNjdFQiIsICIjRkY2M0I2IikKbmFtZXMoY2VsbF9jb2xvdXJzKSAgPC0gYygiRXBpdGhlbGl1bSIsICJGaWJyb2JsYXN0cyIsICJULUNlbGxzIiwgICJNeW9maWJyb2JsYXN0cyIsICJNYWNyb3BoYWdlcyIsICJHbGlhIiwgIkVuZG90aGVsaXVtIiwgIlRlbG9jeXRlcyIsICJQbGFzbWEiLCAiQi1DZWxscyIsICJQZXJpY3l0ZXMiKQpuYW1lcyhjZWxsX2NvbG91cnMpIDwtIHNldXJhdF9DUkMyJHByZWRpY3RlZC5pZCAlPiUgdW5pcXVlKCkKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBjb2xzID0gY2VsbF9jb2xvdXJzLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWQiKQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZCIpCmBgYAoKYGBge3J9Cm1lcmdlZCA8LSByZWFkUkRTKCJDUkNfbWVyZ2UucmRzIikKCkNlbGxwcm9wUGxvdChzZXVyYXRfQ1JDMiwgZ3JvdXAuYnk9ICJwcmVkaWNpdGVkLmlkIiwgcHJvcC5pbj0iIikKYGBgCgoKCgoKCgo=